This commit is contained in:
2025-07-26 10:10:52 +08:00
parent 9f83969584
commit 81c11d3f30
21 changed files with 1095 additions and 1 deletions

16
pam/Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
[package]
name = "pam-bindings"
description = "PAM bindings for Rust"
version = "0.1.1"
authors = [ "Anthony Nowell <anowell@gmail.com>" ]
repository = "https://github.com/anowell/pam-rs"
readme = "../README.md"
keywords = ["pam", "ffi", "linux", "authentication"]
license = "MIT"
[lib]
name = "pam"
[dependencies]
libc = "0.2.97"

66
pam/src/constants.rs Normal file
View File

@@ -0,0 +1,66 @@
use libc::{c_int, c_uint};
// TODO: Import constants from C header file at compile time.
pub type PamFlag = c_uint;
pub type PamItemType = c_int;
pub type PamMessageStyle = c_int;
// The Linux-PAM flags
// see /usr/include/security/_pam_types.h
pub const PAM_SILENT: PamFlag = 0x8000;
pub const PAM_DISALLOW_NULL_AUTHTOK: PamFlag = 0x0001;
pub const PAM_ESTABLISH_CRED: PamFlag = 0x0002;
pub const PAM_DELETE_CRED: PamFlag = 0x0004;
pub const PAM_REINITIALIZE_CRED: PamFlag = 0x0008;
pub const PAM_REFRESH_CRED: PamFlag = 0x0010;
pub const PAM_CHANGE_EXPIRED_AUTHTOK: PamFlag = 0x0020;
// Message styles
pub const PAM_PROMPT_ECHO_OFF: PamMessageStyle = 1;
pub const PAM_PROMPT_ECHO_ON: PamMessageStyle = 2;
pub const PAM_ERROR_MSG: PamMessageStyle = 3;
pub const PAM_TEXT_INFO: PamMessageStyle = 4;
/// yes/no/maybe conditionals
pub const PAM_RADIO_TYPE: PamMessageStyle = 5;
pub const PAM_BINARY_PROMPT: PamMessageStyle = 7;
// The Linux-PAM return values
// see /usr/include/security/_pam_types.h
#[allow(non_camel_case_types, dead_code)]
#[derive(Debug, PartialEq)]
#[repr(C)]
pub enum PamResultCode {
PAM_SUCCESS = 0,
PAM_OPEN_ERR = 1,
PAM_SYMBOL_ERR = 2,
PAM_SERVICE_ERR = 3,
PAM_SYSTEM_ERR = 4,
PAM_BUF_ERR = 5,
PAM_PERM_DENIED = 6,
PAM_AUTH_ERR = 7,
PAM_CRED_INSUFFICIENT = 8,
PAM_AUTHINFO_UNAVAIL = 9,
PAM_USER_UNKNOWN = 10,
PAM_MAXTRIES = 11,
PAM_NEW_AUTHTOK_REQD = 12,
PAM_ACCT_EXPIRED = 13,
PAM_SESSION_ERR = 14,
PAM_CRED_UNAVAIL = 15,
PAM_CRED_EXPIRED = 16,
PAM_CRED_ERR = 17,
PAM_NO_MODULE_DATA = 18,
PAM_CONV_ERR = 19,
PAM_AUTHTOK_ERR = 20,
PAM_AUTHTOK_RECOVERY_ERR = 21,
PAM_AUTHTOK_LOCK_BUSY = 22,
PAM_AUTHTOK_DISABLE_AGING = 23,
PAM_TRY_AGAIN = 24,
PAM_IGNORE = 25,
PAM_ABORT = 26,
PAM_AUTHTOK_EXPIRED = 27,
PAM_MODULE_UNKNOWN = 28,
PAM_BAD_ITEM = 29,
PAM_CONV_AGAIN = 30,
PAM_INCOMPLETE = 31,
}

94
pam/src/conv.rs Normal file
View File

@@ -0,0 +1,94 @@
use libc::{c_char, c_int};
use std::ffi::{CStr, CString};
use std::ptr;
use constants::PamResultCode;
use constants::PamMessageStyle;
use items::Item;
use module::PamResult;
#[repr(C)]
struct PamMessage {
msg_style: PamMessageStyle,
msg: *const c_char,
}
#[repr(C)]
struct PamResponse {
resp: *const c_char,
resp_retcode: libc::c_int, // Unused - always zero
}
/// `PamConv` acts as a channel for communicating with 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.
#[repr(C)]
pub struct Inner {
conv: extern "C" fn(
num_msg: c_int,
pam_message: &&PamMessage,
pam_response: &mut *const PamResponse,
appdata_ptr: *const libc::c_void,
) -> PamResultCode,
appdata_ptr: *const libc::c_void,
}
pub struct Conv<'a>(&'a Inner);
impl<'a> Conv<'a> {
/// 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: PamMessageStyle, msg: &str) -> PamResult<Option<&CStr>> {
let mut resp_ptr: *const PamResponse = ptr::null();
let msg_cstr = CString::new(msg).unwrap();
let msg = PamMessage {
msg_style: style,
msg: msg_cstr.as_ptr(),
};
let ret = (self.0.conv)(1, &&msg, &mut resp_ptr, self.0.appdata_ptr);
if PamResultCode::PAM_SUCCESS == ret {
// PamResponse.resp is null for styles that don't return user input like PAM_TEXT_INFO
let response = unsafe { (*resp_ptr).resp };
if response.is_null() {
Ok(None)
} else {
Ok(Some(unsafe { CStr::from_ptr(response) }))
}
} else {
Err(ret)
}
}
}
impl<'a> Item for Conv<'a> {
type Raw = Inner;
fn type_id() -> crate::items::ItemType {
crate::items::ItemType::Conv
}
unsafe fn from_raw(raw: *const Self::Raw) -> Self {
Self(&*raw)
}
fn into_raw(self) -> *const Self::Raw {
self.0 as _
}
}

88
pam/src/items.rs Normal file
View File

@@ -0,0 +1,88 @@
#[repr(u32)]
pub enum ItemType {
/// The service name
Service = 1,
/// The user name
User = 2,
/// The tty name
Tty = 3,
/// The remote host name
RHost = 4,
/// The pam_conv structure
Conv = 5,
/// The authentication token (password)
AuthTok = 6,
/// The old authentication token
OldAuthTok = 7,
/// The remote user name
RUser = 8,
/// the prompt for getting a username
UserPrompt = 9,
/// app supplied function to override failure delays
FailDelay = 10,
/// X :display name
XDisplay = 11,
/// X :server authentication data
XAuthData = 12,
/// The type for pam_get_authtok
AuthTokType = 13,
}
// A type that can be requested by `pam::Handle::get_item`.
pub trait Item {
/// The `repr(C)` type that is returned (by pointer) by the underlying `pam_get_item` function.
type Raw;
/// The `ItemType` for this type
fn type_id() -> ItemType;
/// The function to convert from the pointer to the C-representation to this safer wrapper type
///
/// # Safety
///
/// This function can assume the pointer is a valid pointer to a `Self::Raw` instance.
unsafe fn from_raw(raw: *const Self::Raw) -> Self;
/// The function to convert from this wrapper type to a C-compatible pointer.
fn into_raw(self) -> *const Self::Raw;
}
macro_rules! cstr_item {
($name:ident) => {
#[derive(Debug)]
pub struct $name<'s>(pub &'s std::ffi::CStr);
impl<'s> std::ops::Deref for $name<'s> {
type Target = &'s std::ffi::CStr;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'s> Item for $name<'s> {
type Raw = libc::c_char;
fn type_id() -> ItemType {
ItemType::$name
}
unsafe fn from_raw(raw: *const Self::Raw) -> Self {
Self(std::ffi::CStr::from_ptr(raw))
}
fn into_raw(self) -> *const Self::Raw {
self.0.as_ptr()
}
}
};
}
cstr_item!(Service);
cstr_item!(User);
cstr_item!(Tty);
cstr_item!(RHost);
// Conv
cstr_item!(AuthTok);
cstr_item!(OldAuthTok);
cstr_item!(RUser);
cstr_item!(UserPrompt);

34
pam/src/lib.rs Executable file
View File

@@ -0,0 +1,34 @@
//! Interface to the pluggable authentication module framework (PAM).
//!
//! The goal of this library is to provide a type-safe API that can be used to
//! interact with PAM. The library is incomplete - currently it supports
//! a subset of functions for use in a pam authentication module. A pam module
//! is a shared library that is invoked to authenticate a user, or to perform
//! other functions.
//!
//! For general information on writing pam modules, see
//! [The Linux-PAM Module Writers' Guide][module-guide]
//!
//! [module-guide]: http://www.linux-pam.org/Linux-PAM-html/Linux-PAM_MWG.html
//!
//! A typical authentication module will define an external function called
//! `pam_sm_authenticate()`, which will use functions in this library to
//! interrogate the program that requested authentication for more information,
//! and to render a result. For a working example that uses this library, see
//! [toznyauth-pam][].
//!
//! [toznyauth-pam]: https://github.com/tozny/toznyauth-pam
//!
//! Note that constants that are normally read from pam header files are
//! hard-coded in the `constants` module. The values there are taken from
//! a Linux system. That means that it might take some work to get this library
//! to work on other platforms.
extern crate libc;
pub mod constants;
pub mod conv;
pub mod items;
#[doc(hidden)]
pub mod macros;
pub mod module;

141
pam/src/macros.rs Normal file
View File

@@ -0,0 +1,141 @@
/// Macro to generate the `extern "C"` entrypoint bindings needed by PAM
///
/// You can call `pam_hooks!(SomeType);` for any type that implements `PamHooks`
///
/// ## Examples:
///
/// Here is full example of a PAM module that would authenticate and authorize everybody:
///
/// ```
/// #[macro_use] extern crate pam;
///
/// use pam::module::{PamHooks, PamHandle};
/// use pam::constants::{PamResultCode, PamFlag};
/// use std::ffi::CStr;
///
/// # fn main() {}
/// struct MyPamModule;
/// pam_hooks!(MyPamModule);
///
/// impl PamHooks for MyPamModule {
/// fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
/// println!("Everybody is authenticated!");
/// PamResultCode::PAM_SUCCESS
/// }
///
/// fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
/// println!("Everybody is authorized!");
/// PamResultCode::PAM_SUCCESS
/// }
/// }
/// ```
#[macro_export]
macro_rules! pam_hooks {
($ident:ident) => {
pub use self::pam_hooks_scope::*;
mod pam_hooks_scope {
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use $crate::constants::{PamFlag, PamResultCode};
use $crate::module::{PamHandle, PamHooks};
fn extract_argv<'a>(argc: c_int, argv: *const *const c_char) -> Vec<&'a CStr> {
(0..argc)
.map(|o| unsafe { CStr::from_ptr(*argv.offset(o as isize) as *const c_char) })
.collect()
}
#[no_mangle]
pub extern "C" fn pam_sm_acct_mgmt(
pamh: &mut PamHandle,
flags: PamFlag,
argc: c_int,
argv: *const *const c_char,
) -> PamResultCode {
let args = extract_argv(argc, argv);
super::$ident::acct_mgmt(pamh, args, flags)
}
#[no_mangle]
pub extern "C" fn pam_sm_authenticate(
pamh: &mut PamHandle,
flags: PamFlag,
argc: c_int,
argv: *const *const c_char,
) -> PamResultCode {
let args = extract_argv(argc, argv);
super::$ident::sm_authenticate(pamh, args, flags)
}
#[no_mangle]
pub extern "C" fn pam_sm_chauthtok(
pamh: &mut PamHandle,
flags: PamFlag,
argc: c_int,
argv: *const *const c_char,
) -> PamResultCode {
let args = extract_argv(argc, argv);
super::$ident::sm_chauthtok(pamh, args, flags)
}
#[no_mangle]
pub extern "C" fn pam_sm_close_session(
pamh: &mut PamHandle,
flags: PamFlag,
argc: c_int,
argv: *const *const c_char,
) -> PamResultCode {
let args = extract_argv(argc, argv);
super::$ident::sm_close_session(pamh, args, flags)
}
#[no_mangle]
pub extern "C" fn pam_sm_open_session(
pamh: &mut PamHandle,
flags: PamFlag,
argc: c_int,
argv: *const *const c_char,
) -> PamResultCode {
let args = extract_argv(argc, argv);
super::$ident::sm_open_session(pamh, args, flags)
}
#[no_mangle]
pub extern "C" fn pam_sm_setcred(
pamh: &mut PamHandle,
flags: PamFlag,
argc: c_int,
argv: *const *const c_char,
) -> PamResultCode {
let args = extract_argv(argc, argv);
super::$ident::sm_setcred(pamh, args, flags)
}
}
};
}
#[macro_export]
macro_rules! pam_try {
($r:expr) => {
match $r {
Ok(t) => t,
Err(e) => return e,
}
};
($r:expr, $e:expr) => {
match $r {
Ok(t) => t,
Err(_) => return $e,
}
};
}
#[cfg(test)]
pub mod test {
use module::PamHooks;
struct Foo;
impl PamHooks for Foo {}
pam_hooks!(Foo);
}

255
pam/src/module.rs Executable file
View File

@@ -0,0 +1,255 @@
//! Functions for use in pam modules.
use libc::c_char;
use std::ffi::{CStr, CString};
use constants::{PamFlag, PamResultCode};
/// Opaque type, used as a pointer when making pam API calls.
///
/// A module is invoked via an external function such as `pam_sm_authenticate`.
/// Such a call provides a pam handle pointer. The same pointer should be given
/// as an argument when making API calls.
#[repr(C)]
pub struct PamHandle {
_data: [u8; 0],
}
#[link(name = "pam")]
extern "C" {
fn pam_get_data(
pamh: *const PamHandle,
module_data_name: *const c_char,
data: &mut *const libc::c_void,
) -> PamResultCode;
fn pam_set_data(
pamh: *const PamHandle,
module_data_name: *const c_char,
data: *mut libc::c_void,
cleanup: extern "C" fn(
pamh: *const PamHandle,
data: *mut libc::c_void,
error_status: PamResultCode,
),
) -> PamResultCode;
fn pam_get_item(
pamh: *const PamHandle,
item_type: crate::items::ItemType,
item: &mut *const libc::c_void,
) -> PamResultCode;
fn pam_set_item(
pamh: *mut PamHandle,
item_type: crate::items::ItemType,
item: *const libc::c_void,
) -> PamResultCode;
fn pam_get_user(
pamh: *const PamHandle,
user: &*mut c_char,
prompt: *const c_char,
) -> PamResultCode;
}
pub extern "C" fn cleanup<T>(_: *const PamHandle, c_data: *mut libc::c_void, _: PamResultCode) {
unsafe {
let _data: Box<T> = Box::from_raw(c_data.cast::<T>());
}
}
pub type PamResult<T> = Result<T, PamResultCode>;
impl PamHandle {
/// Gets some value, identified by `key`, that has been set by the module
/// previously.
///
/// See `pam_get_data` in
/// http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html
///
/// # Errors
///
/// Returns an error if the underlying PAM function call fails.
///
/// # Safety
///
/// The data stored under the provided key must be of type `T` otherwise the
/// behaviour of this funtion is undefined.
pub unsafe fn get_data<'a, T>(&'a self, key: &str) -> PamResult<&'a T> {
let c_key = CString::new(key).unwrap();
let mut ptr: *const libc::c_void = std::ptr::null();
let res = pam_get_data(self, c_key.as_ptr(), &mut ptr);
if PamResultCode::PAM_SUCCESS == res && !ptr.is_null() {
let typed_ptr = ptr.cast::<T>();
let data: &T = &*typed_ptr;
Ok(data)
} else {
Err(res)
}
}
/// Stores a value that can be retrieved later with `get_data`. The value lives
/// as long as the current pam cycle.
///
/// See `pam_set_data` in
/// http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html
///
/// # Errors
///
/// Returns an error if the underlying PAM function call fails.
pub fn set_data<T>(&self, key: &str, data: Box<T>) -> PamResult<()> {
let c_key = CString::new(key).unwrap();
let res = unsafe {
pam_set_data(
self,
c_key.as_ptr(),
Box::into_raw(data).cast::<libc::c_void>(),
cleanup::<T>,
)
};
if PamResultCode::PAM_SUCCESS == res {
Ok(())
} else {
Err(res)
}
}
/// Retrieves a value that has been set, possibly by the pam client. This is
/// particularly useful for getting a `PamConv` reference.
///
/// See `pam_get_item` in
/// http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html
///
/// # Errors
///
/// Returns an error if the underlying PAM function call fails.
pub fn get_item<T: crate::items::Item>(&self) -> PamResult<Option<T>> {
let mut ptr: *const libc::c_void = std::ptr::null();
let (res, item) = unsafe {
let r = pam_get_item(self, T::type_id(), &mut ptr);
let typed_ptr = ptr.cast::<T::Raw>();
let t = if typed_ptr.is_null() {
None
} else {
Some(T::from_raw(typed_ptr))
};
(r, t)
};
if PamResultCode::PAM_SUCCESS == res {
Ok(item)
} else {
Err(res)
}
}
/// Sets a value in the pam context. The value can be retrieved using
/// `get_item`.
///
/// Note that all items are strings, except `PAM_CONV` and `PAM_FAIL_DELAY`.
///
/// See `pam_set_item` in
/// http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html
///
/// # Errors
///
/// Returns an error if the underlying PAM function call fails.
///
/// # Panics
///
/// Panics if the provided item key contains a nul byte
pub fn set_item_str<T: crate::items::Item>(&mut self, item: T) -> PamResult<()> {
let res =
unsafe { pam_set_item(self, T::type_id(), item.into_raw().cast::<libc::c_void>())};
if PamResultCode::PAM_SUCCESS == res {
Ok(())
} else {
Err(res)
}
}
/// Retrieves the name of the user who is authenticating or logging in.
///
/// This is really a specialization of `get_item`.
///
/// See `pam_get_user` in
/// http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html
///
/// # Errors
///
/// Returns an error if the underlying PAM function call fails.
///
/// # Panics
///
/// Panics if the provided prompt string contains a nul byte
pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> {
let ptr: *mut c_char = std::ptr::null_mut();
let prompt_string;
let c_prompt = match prompt {
Some(p) => {
prompt_string = CString::new(p).unwrap();
prompt_string.as_ptr()
}
None => std::ptr::null(),
};
let res = unsafe { pam_get_user(self, &ptr, c_prompt) };
if PamResultCode::PAM_SUCCESS == res && !ptr.is_null() {
let const_ptr = ptr as *const c_char;
let bytes = unsafe { CStr::from_ptr(const_ptr).to_bytes() };
String::from_utf8(bytes.to_vec()).map_err(|_| PamResultCode::PAM_CONV_ERR)
} else {
Err(res)
}
}
}
/// Provides functions that are invoked by the entrypoints generated by the
/// [`pam_hooks!` macro](../macro.pam_hooks.html).
///
/// All of hooks are ignored by PAM dispatch by default given the default return value of `PAM_IGNORE`.
/// Override any functions that you want to handle with your module. See `man pam(3)`.
#[allow(unused_variables)]
pub trait PamHooks {
/// This function performs the task of establishing whether the user is permitted to gain access at
/// this time. It should be understood that the user has previously been validated by an
/// authentication module. This function checks for other things. Such things might be: the time of
/// day or the date, the terminal line, remote hostname, etc. This function may also determine
/// things like the expiration on passwords, and respond that the user change it before continuing.
fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
/// This function performs the task of authenticating the user.
fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
/// This function is used to (re-)set the authentication token of the user.
///
/// The PAM library calls this function twice in succession. The first time with
/// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with
/// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is
/// (possibly) changed.
fn sm_chauthtok(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
/// This function is called to terminate a session.
fn sm_close_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
/// This function is called to commence a session.
fn sm_open_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
/// This function performs the task of altering the credentials of the user with respect to the
/// corresponding authorization scheme. Generally, an authentication module may have access to more
/// information about a user than their authentication token. This function is used to make such
/// information available to the application. It should only be called after the user has been
/// authenticated but before a session has been established.
fn sm_setcred(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
}