kanidm/unix_integration/pam_kanidm/src/pam/module.rs

370 lines
12 KiB
Rust
Executable file

//! Functions for use in pam modules.
use std::ffi::{CStr, CString};
use std::{mem, ptr};
use libc::c_char;
use crate::pam::constants::{
PamFlag, PamItemType, PamResultCode, PAM_PROMPT_ECHO_OFF, PAM_TEXT_INFO,
};
use crate::pam::conv::PamConv;
use crate::pam::items::{PamAuthTok, PamRHost, PamService, PamTty};
use crate::core::PamHandler;
use kanidm_unix_common::unix_proto::DeviceAuthorizationResponse;
use kanidm_unix_common::unix_proto::PamServiceInfo;
/// 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.
#[allow(missing_copy_implementations)]
pub enum PamHandle {}
#[allow(missing_copy_implementations)]
enum PamItemT {}
#[allow(missing_copy_implementations)]
pub enum PamDataT {}
#[link(name = "pam")]
extern "C" {
/*
fn pam_get_data(
pamh: *const PamHandle,
module_data_name: *const c_char,
data: &mut *const PamDataT,
) -> PamResultCode;
fn pam_set_data(
pamh: *const PamHandle,
module_data_name: *const c_char,
data: *mut PamDataT,
cleanup: unsafe extern "C" fn(
pamh: *const PamHandle,
data: *mut PamDataT,
error_status: PamResultCode,
),
) -> PamResultCode;
*/
fn pam_get_item(
pamh: *const PamHandle,
item_type: PamItemType,
item: &mut *const PamItemT,
) -> PamResultCode;
/*
fn pam_set_item(pamh: *mut PamHandle, item_type: PamItemType, item: &PamItemT)
-> PamResultCode;
*/
fn pam_get_user(
pamh: *const PamHandle,
user: &mut *const c_char,
prompt: *const c_char,
) -> PamResultCode;
}
/// # Safety
///
/// We're doing what we can for this one, but it's FFI.
pub unsafe extern "C" fn cleanup<T>(_: *const PamHandle, c_data: *mut PamDataT, _: PamResultCode) {
let c_data = Box::from_raw(c_data);
let data: Box<T> = mem::transmute(c_data);
mem::drop(data);
}
pub type PamResult<T> = Result<T, PamResultCode>;
/// # Safety
///
/// Type-level mapping for safely retrieving values with `get_item`.
///
/// See `pam_get_item` in
/// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
pub trait PamItem {
/// Maps a Rust type to a pam constant.
///
/// For example, the type PamConv maps to the constant PAM_CONV. The pam
/// API contract specifies that when the API function `pam_get_item` is
/// called with the constant PAM_CONV, it will return a value of type
/// `PamConv`.
fn item_type() -> PamItemType;
}
impl PamHandle {
/*
/// # Safety
///
/// 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>
unsafe fn get_data<'a, T>(&'a self, key: &str) -> PamResult<&'a T> {
let c_key = CString::new(key).unwrap();
let mut ptr: *const PamDataT = 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: *const T = ptr as *const 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>
fn set_data<T>(&self, key: &str, data: Box<T>) -> PamResult<()> {
let c_key = CString::new(key).unwrap();
let res = unsafe {
let c_data: Box<PamDataT> = mem::transmute(data);
let c_data = Box::into_raw(c_data);
pam_set_data(self, c_key.as_ptr(), c_data, 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>
fn get_item<'a, T: PamItem>(&self) -> PamResult<&'a T> {
let mut ptr: *const PamItemT = ptr::null();
let (res, item) = unsafe {
let r = pam_get_item(self, T::item_type(), &mut ptr);
let typed_ptr: *const T = ptr as *const T;
let t: &T = &*typed_ptr;
(r, t)
};
if PamResultCode::PAM_SUCCESS == res {
Ok(item)
} else {
Err(res)
}
}
fn get_item_string<T: PamItem>(&self) -> PamResult<Option<String>> {
let mut ptr: *const PamItemT = ptr::null();
let (res, item) = unsafe {
let r = pam_get_item(self, T::item_type(), &mut ptr);
let t = if PamResultCode::PAM_SUCCESS == r && !ptr.is_null() {
let typed_ptr: *const c_char = ptr as *const c_char;
Some(CStr::from_ptr(typed_ptr).to_string_lossy().into_owned())
} else {
None
};
(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>
fn set_item_str<T: PamItem>(&mut self, item: &str) -> PamResult<()> {
let c_item = CString::new(item).unwrap();
let res = unsafe {
pam_set_item(
self,
T::item_type(),
// unwrapping is okay here, as c_item will not be a NULL
// pointer
(c_item.as_ptr() as *const PamItemT).as_ref().unwrap(),
)
};
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>
fn get_user(&self, prompt: Option<&str>) -> PamResult<String> {
let mut ptr: *const c_char = ptr::null_mut();
let res = match prompt {
Some(p) => {
let c_prompt = CString::new(p).map_err(|_| PamResultCode::PAM_CONV_ERR)?;
unsafe { pam_get_user(self, &mut ptr, c_prompt.as_ptr()) }
}
None => unsafe { pam_get_user(self, &mut ptr, ptr::null()) },
};
if PamResultCode::PAM_SUCCESS == res {
if ptr.is_null() {
Err(PamResultCode::PAM_AUTHINFO_UNAVAIL)
} else {
let bytes = unsafe { CStr::from_ptr(ptr).to_bytes() };
String::from_utf8(bytes.to_vec()).map_err(|_| PamResultCode::PAM_CONV_ERR)
}
} else {
Err(res)
}
}
fn get_authtok(&self) -> PamResult<Option<String>> {
self.get_item_string::<PamAuthTok>()
}
fn get_tty(&self) -> PamResult<Option<String>> {
self.get_item_string::<PamTty>()
}
fn get_rhost(&self) -> PamResult<Option<String>> {
self.get_item_string::<PamRHost>()
}
fn get_service(&self) -> PamResult<Option<String>> {
self.get_item_string::<PamService>()
}
fn get_pam_info(&self) -> PamResult<PamServiceInfo> {
let maybe_tty = self.get_tty()?;
let maybe_rhost = self.get_rhost()?;
let maybe_service = self.get_service()?;
tracing::debug!(?maybe_tty, ?maybe_rhost, ?maybe_service);
match (maybe_tty, maybe_rhost, maybe_service) {
(Some(tty), Some(rhost), Some(service)) => Ok(PamServiceInfo {
service,
tty,
rhost,
}),
_ => Err(PamResultCode::PAM_CONV_ERR),
}
}
fn get_conv(&self) -> PamResult<&PamConv> {
self.get_item::<PamConv>()
}
}
impl PamHandler for PamHandle {
fn account_id(&self) -> PamResult<String> {
self.get_user(None)
}
fn service_info(&self) -> PamResult<PamServiceInfo> {
self.get_pam_info()
}
fn authtok(&self) -> PamResult<Option<String>> {
self.get_authtok()
}
fn message(&self, msg: &str) -> PamResult<()> {
let conv = self.get_conv()?;
conv.send(PAM_TEXT_INFO, msg).map(|_| ())
}
fn prompt_for_password(&self) -> PamResult<Option<String>> {
let conv = self.get_conv()?;
conv.send(PAM_PROMPT_ECHO_OFF, "Password: ")
}
fn prompt_for_mfacode(&self) -> PamResult<Option<String>> {
let conv = self.get_conv()?;
conv.send(PAM_PROMPT_ECHO_OFF, "Code: ")
}
fn prompt_for_pin(&self, msg: Option<&str>) -> PamResult<Option<String>> {
let conv = self.get_conv()?;
let msg = msg.unwrap_or("PIN: ");
conv.send(PAM_PROMPT_ECHO_OFF, msg)
}
fn message_device_grant(&self, data: &DeviceAuthorizationResponse) -> PamResult<()> {
let conv = self.get_conv()?;
let msg = match &data.message {
Some(msg) => msg.clone(),
None => format!(
"Using a browser on another device, visit:\n{}\nAnd enter the code:\n{}",
data.verification_uri, data.user_code
),
};
conv.send(PAM_TEXT_INFO, &msg).map(|_| ())
}
}
/// 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: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
/// This function performs the task of authenticating the user.
fn sm_authenticate(pamh: &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: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
/// This function is called to terminate a session.
fn sm_close_session(pamh: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
/// This function is called to commence a session.
fn sm_open_session(pamh: &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: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
}