view libpam-sys/src/ffi.rs @ 131:a632a8874131

Get all the Linux-PAM functions into libpam-sys, and get tests right.
author Paul Fisher <paul@pfish.zone>
date Wed, 02 Jul 2025 02:24:21 -0400
parents 80c07e5ab22f
children
line wrap: on
line source

//! The actual PAM FFI bindings.
//!
//! They live in this specific file rather than lib.rs because otherwise
//! ctest gets very upset about some of the macros we use.
#![allow(non_camel_case_types)]
#![allow(unused_imports)]

use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::ffi::{c_char, c_int, c_uint, c_void};
use std::fmt;
use std::marker::{PhantomData, PhantomPinned};

/// A marker struct to make whatever it's in `!Sync`, `!Send`, and `!Unpin`.
#[derive(Default, PartialOrd, PartialEq, Ord, Eq)]
#[repr(C)]
struct ExtremelyUnsafe {
    _value: (),
    _marker: PhantomData<(PhantomPinned, *mut c_void)>,
}

impl fmt::Debug for ExtremelyUnsafe {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "ExtremelyUnsafe({self:p})")
    }
}

/// An opaque structure that PAM uses to communicate.
///
/// This is only ever returned in pointer form and cannot be constructed.
#[repr(C)]
pub struct pam_handle(ExtremelyUnsafe);

impl fmt::Debug for pam_handle {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "pam_handle({self:p}")
    }
}

/// An opaque structure that is passed through PAM in a conversation.
#[repr(C)]
pub struct AppData(ExtremelyUnsafe);

impl fmt::Debug for AppData {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "AppData({self:p}")
    }
}

/// Just an alias for the type of [`pam_conv::conv`].
///
/// For important details about the format of `messages`,
/// see the [`helpers`](crate::helpers) module.
///
/// ```no_run
/// # use libpam_sys::{ConversationCallback, pam_conv};
/// fn convo() -> ConversationCallback {
///     // ...
/// #    unimplemented!()
/// }
/// let conv = pam_conv{conv: convo(), appdata_ptr: std::ptr::null_mut()};
/// ```
pub type ConversationCallback = unsafe extern "C" fn(
    num_msg: c_int,
    msg: *const *const pam_message,
    resp: *mut *mut pam_response,
    appdata: *mut AppData,
) -> c_int;

/// Alias for the callback to [`pam_set_data`](crate::pam_set_data).
///
/// ```no_run
/// # use std::ffi::CString;
/// use libpam_sys::{CleanupCallback, pam_set_data};
/// # use libpam_sys::pam_handle;
/// # let handle: *mut pam_handle = std::ptr::null_mut();
/// # let mut my_data = 100;
/// # let data_ptr = &mut my_data as *mut i32;
/// fn cleanup() -> CleanupCallback {
///     // ...
/// #    unimplemented!()
/// }
/// let name = CString::new("name").unwrap();
/// unsafe {
///     pam_set_data(handle, name.as_ptr().cast_mut(), data_ptr.cast(), cleanup());
/// }
/// ```
pub type CleanupCallback =
    unsafe extern "C" fn(pamh: *mut pam_handle, data: *mut c_void, pam_end_status: c_int);

/// Used by PAM to communicate between the module and the application.
#[repr(C)]
pub struct pam_conv {
    pub conv: unsafe extern "C" fn(
        num_msg: c_int,
        msg: *const *const pam_message,
        resp: *mut *mut pam_response,
        appdata: *mut AppData,
    ) -> c_int,
    pub appdata_ptr: *mut AppData,
}

/// A message sent into a PAM conversation.
#[repr(C)]
pub struct pam_message {
    pub msg_style: c_int,
    pub msg: *const c_char,
}

/// A response returned from a PAM conversation.
#[repr(C)]
pub struct pam_response {
    pub resp: *mut c_char,
    /// Completely unused.
    pub resp_retcode: c_int,
}

/// Definition of the PAM_XAUTHDATA item. Compatible with `xcb_auth_info_t`.
#[cfg(_hack_impl = "LinuxPam")]
#[repr(C)]
pub struct pam_xauth_data {
    namelen: c_int,
    name: *mut c_char,
    datalen: c_int,
    data: *mut c_char,
}

#[cfg(_hack_impl = "LinuxPam")]
#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[repr(i32)]
pub enum pam_modutil_redirect_fd {
    PAM_MODUTIL_IGNORE_FD,
    PAM_MODUTIL_PIPE_FD,
    PAM_MODUTIL_NULL_FD,
}

#[cfg(_hack_impl = "LinuxPam")]
pub use pam_modutil_redirect_fd::*;

#[cfg(_hack_impl = "LinuxPam")]
#[derive(Debug)]
#[repr(C)]
pub struct pam_modutil_privs {
    grplist: *mut libc::gid_t,
    number_of_groups: c_int,
    allocated: c_int,
    old_gid: libc::gid_t,
    old_uid: libc::uid_t,
    is_dropped: c_int,
}

// These are the functions specified in X/SSO. Everybody exports them.
extern "C" {
    /// Account validation.
    pub fn pam_acct_mgmt(pamh: *mut pam_handle, flags: c_int) -> c_int;

    /// Authenticate a user.
    pub fn pam_authenticate(pamh: *mut pam_handle, flags: c_int) -> c_int;

    // Nobody implements pam_authenticate_secondary.

    /// Manage authentication tokens.
    pub fn pam_chauthtok(pamh: *mut pam_handle, flags: c_int) -> c_int;

    /// Close an opened user session.
    pub fn pam_close_session(pamh: *mut pam_handle, flags: c_int) -> c_int;

    /// Ends the PAM transaction.
    pub fn pam_end(pamh: *mut pam_handle, flags: c_int) -> c_int;

    /// Gets module-specific data. PAM still owns the data.
    pub fn pam_get_data(
        pamh: *mut pam_handle,
        module_data_name: *const c_char,
        data: *mut *const c_void,
    ) -> c_int;

    /// Gets an environment variable.  You own the return value.
    pub fn pam_getenv(pamh: *const pam_handle, name: *const c_char) -> *mut c_char;

    /// Gets all the environment variables.  You own everything it points to.
    pub fn pam_getenvlist(pamh: *const pam_handle) -> *mut *mut c_char;

    /// Get information about the transaction.
    ///
    /// The item is owned by PAM.
    pub fn pam_get_item(
        pamh: *const pam_handle,
        item_type: c_int,
        item: *mut *const c_void,
    ) -> c_int;

    // Nobody implements pam_get_mapped_authtok.
    // Nobody implements pam_get_mapped_username.

    /// Get the username. PAM owns it.
    pub fn pam_get_user(
        pamh: *mut pam_handle,
        user: *mut *const c_char,
        prompt: *const c_char,
    ) -> c_int;

    /// Opens a user session.
    pub fn pam_open_session(pamh: *mut pam_handle, flags: c_int) -> c_int;

    /// Sets the value of an environment variable. `namevalue` is copied.
    pub fn pam_putenv(pamh: *mut pam_handle, namevalue: *const c_char) -> c_int;

    /// Update or delete user credentials.
    pub fn pam_setcred(pamh: *mut pam_handle, flags: c_int) -> c_int;

    /// Set module-specific data. PAM will call `cleanup` when completed.
    pub fn pam_set_data(
        pamh: *mut pam_handle,
        module_data_name: *const c_char,
        data: *mut c_void,
        cleanup: unsafe extern "C" fn(
            pamh: *mut pam_handle,
            data: *mut c_void,
            pam_end_status: c_int,
        ),
    ) -> c_int;

    /// Set information about the transaction.  The `item` is copied.
    pub fn pam_set_item(pamh: *mut pam_handle, item_type: c_int, item: *const c_void) -> c_int;

    // Nobody implements pam_set_mapped_authtok.
    // Nobody implements pam_set_mapped_username.

    // The pam_sm_whatever functions are prototypes for the functions that
    // a PAM module should implement, not symbols provided by PAM.

    // Nobody implements pam_authenticate_secondary.

    /// Starts a PAM transaction.  The `conv` may or may not be copied.
    pub fn pam_start(
        service: *const c_char,
        user: *const c_char,
        pam_conv: *mut pam_conv,
        pamh: *mut *mut pam_handle,
    ) -> c_int;

    /// Gets a statically-allocated error string.
    ///
    /// All implementations of PAM known to this library (Linux-PAM, OpenPAM,
    /// and Sun) ignore `pamh` and will accept a null pointer.
    pub fn pam_strerror(pamh: *const pam_handle, error_number: c_int) -> *mut c_char;
}

// We use `_hack_impl` because ctest loses its mind
// when it comes across the `cfg_pam_impl` macro.
// This is a custom cfg variable set in our build.rs. Don't do this; just use
// cfg_pam_impl.
#[cfg(any(_hack_impl = "LinuxPam", _hack_impl = "OpenPam"))]
extern "C" {
    /// Gets `PAM_AUTHTOK`, or asks the user if that is unset.
    pub fn pam_get_authtok(
        pamh: *mut pam_handle,
        item: c_int,
        authtok: *mut *const c_char,
        prompt: *const c_char,
    ) -> c_int;
}

#[cfg(_hack_impl = "LinuxPam")]
extern "C" {
    pub fn pam_fail_delay(pamh: *mut pam_handle, musec_delay: c_uint) -> c_int;

    /// Start a PAM transaction based on configuration in the given directory.
    pub fn pam_start_confdir(
        service_name: *const c_char,
        user: *const c_char,
        pam_conversation: *mut pam_conv,
        confdir: *const c_char,
        pamh: *mut *mut pam_handle,
    ) -> c_int;

    // We don't export the v-variants of the formatting functions.

    pub fn pam_syslog(pamh: *const pam_handle, priority: c_int, fmt: *const c_char, ...);

    pub fn pam_prompt(
        pamh: *const pam_handle,
        style: c_int,
        response: *mut *mut c_char,
        fmt: *const c_char,
        ...
    ) -> c_int;

    pub fn pam_get_authtok_noverify(
        pamh: *const pam_handle,
        authtok: *mut *const c_char,
        prompt: *const c_char,
    ) -> c_int;

    pub fn pam_get_authtok_verify(
        pamh: *const pam_handle,
        authtok: *mut *const c_char,
        prompt: *const c_char,
    ) -> c_int;

    // pam_modutil also lives in libpam for Linux.

    pub fn pam_modutil_check_user_in_passwd(
        pamh: *mut pam_handle,
        user_name: *const c_char,
        file_name: *const c_char,
    ) -> c_int;

    pub fn pam_modutil_getpwnam(pamh: *mut pam_handle, user: *const c_char) -> *mut libc::passwd;

    pub fn pam_modutil_getpwuid(pamh: *mut pam_handle, uid: libc::uid_t) -> *mut libc::passwd;

    pub fn pam_modutil_getgrnam(pamh: *mut pam_handle, group: *const c_char) -> *mut libc::group;

    pub fn pam_modutil_getgrgid(pamh: *mut pam_handle, gid: libc::gid_t) -> *mut libc::group;

    pub fn pam_modutil_getspnam(pamh: *mut pam_handle, user: *const c_char) -> *mut libc::spwd;

    pub fn pam_modutil_user_in_group_nam_nam(
        pamh: *mut pam_handle,
        user: *const c_char,
        group: *const c_char,
    ) -> c_int;
    pub fn pam_modutil_user_in_group_nam_gid(
        pamh: *mut pam_handle,
        user: *const c_char,
        group: libc::gid_t,
    ) -> c_int;

    pub fn pam_modutil_user_in_group_uid_nam(
        pamh: *mut pam_handle,
        user: libc::uid_t,
        group: *const c_char,
    ) -> c_int;

    pub fn pam_modutil_user_in_group_uid_gid(
        pamh: *mut pam_handle,
        user: libc::uid_t,
        group: libc::gid_t,
    ) -> c_int;

    pub fn pam_modutil_getlogin(pamh: *mut pam_handle) -> *const c_char;

    pub fn pam_modutil_read(fd: c_int, buffer: *mut c_char, count: c_int) -> c_int;

    pub fn pam_modutil_write(fd: c_int, buffer: *const c_char, count: c_int) -> c_int;

    pub fn pam_modutil_audit_write(
        pamh: *mut pam_handle,
        typ: c_int,
        message: *const c_char,
        retval: c_int,
    ) -> c_int;

    pub fn pam_modutil_drop_priv(
        pamh: *mut pam_handle,
        p: *mut pam_modutil_privs,
        pw: *const libc::passwd,
    ) -> c_int;

    pub fn pam_modutil_regain_priv(pamh: *mut pam_handle, p: *mut pam_modutil_privs) -> c_int;

    pub fn pam_modutil_sanitize_helper_fds(
        pamh: *mut pam_handle,
        redirect_stdin: pam_modutil_redirect_fd,
        redirect_stdout: pam_modutil_redirect_fd,
        redirect_stderr: pam_modutil_redirect_fd,
    ) -> c_int;

    pub fn pam_modutil_search_key(
        pamh: *mut pam_handle,
        file_name: *const c_char,
        key: *const c_char,
    ) -> *mut c_char;
}