mirror of
https://github.com/kanidm/kanidm.git
synced 2025-06-07 00:27:46 +02:00
370 lines
12 KiB
Rust
Executable file
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
|
|
}
|
|
}
|