589 lines
16 KiB
Rust
589 lines
16 KiB
Rust
//! Authentication module
|
|
//!
|
|
//! This module is used by the session manager to authenticate a user
|
|
//! and open a new user session if success
|
|
use crate::CRITICAL;
|
|
use crate::DEBUG;
|
|
use crate::ERR;
|
|
use crate::INFO;
|
|
use crate::WARNING;
|
|
use libpam_sys::pam_acct_mgmt;
|
|
use libpam_sys::pam_authenticate;
|
|
use libpam_sys::pam_close_session;
|
|
use libpam_sys::pam_conv;
|
|
use libpam_sys::pam_end;
|
|
use libpam_sys::pam_handle;
|
|
use libpam_sys::pam_message;
|
|
use libpam_sys::pam_open_session;
|
|
use libpam_sys::pam_response;
|
|
use libpam_sys::pam_set_item;
|
|
use libpam_sys::pam_setcred;
|
|
use libpam_sys::pam_start;
|
|
use libpam_sys::pam_strerror;
|
|
use libpam_sys::PAM_BUF_ERR;
|
|
use libpam_sys::PAM_DELETE_CRED;
|
|
use libpam_sys::PAM_ERROR_MSG;
|
|
use libpam_sys::PAM_ESTABLISH_CRED;
|
|
use libpam_sys::PAM_PROMPT_ECHO_OFF;
|
|
use libpam_sys::PAM_PROMPT_ECHO_ON;
|
|
use libpam_sys::PAM_SUCCESS;
|
|
use libpam_sys::PAM_TEXT_INFO;
|
|
use libpam_sys::PAM_TTY;
|
|
use libpam_sys::PAM_USER;
|
|
use libpam_sys::PAM_XDISPLAY;
|
|
use nix::unistd::execvpe;
|
|
use nix::unistd::setgroups;
|
|
use nix::unistd::User;
|
|
use nix::unistd::{getgrouplist, initgroups, setgid, setuid};
|
|
use std::ffi::{CStr, CString};
|
|
use std::os::raw::c_void;
|
|
use std::ptr;
|
|
|
|
/// PAM based session
|
|
///
|
|
/// Session that use PAM for user authentication and session initializing
|
|
///
|
|
/// # Fields
|
|
///
|
|
/// - `pamh` (`*mut pam_handle`) - low level PAM handle
|
|
/// - `appdata` (`*mut PamAppData`) - Application data
|
|
///
|
|
pub struct PamSession {
|
|
pamh: *mut pam_handle,
|
|
appdata: *mut PamAppData,
|
|
}
|
|
|
|
/// Anonymous session
|
|
///
|
|
/// This type of session will ignore any authentication check
|
|
/// and open immediately a user session based on input user
|
|
///
|
|
/// # Fields
|
|
///
|
|
/// - `username` (`String`) - User of the session
|
|
///
|
|
pub struct AnonymousSession {
|
|
username: String,
|
|
}
|
|
|
|
/// Private application data form PamSession
|
|
///
|
|
/// # Fields
|
|
///
|
|
/// - `username` (`String`) - user name
|
|
/// - `password` (`String`) - password that will be remove after authentication
|
|
///
|
|
#[derive(Clone)]
|
|
struct PamAppData {
|
|
pub username: String,
|
|
pub password: String,
|
|
}
|
|
|
|
unsafe impl Send for PamSession {}
|
|
unsafe impl Sync for PamSession {}
|
|
|
|
/// PAM conversation handle
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `num_msg` (`i32`) - number of messages
|
|
/// - `msg` (`*const *const pam_message`) - list of messages
|
|
/// - `resp` (`*mut *mut pam_response`) - list of response
|
|
/// - `appdata_ptr` (`*mut c_void`) - user data
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `i32` - status code
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// - **This function is `unsafe` because:**
|
|
/// - Low level type conversion
|
|
/// - Memory allocation
|
|
unsafe extern "C" fn pam_conv_handler(
|
|
num_msg: i32,
|
|
msg: *const *const pam_message,
|
|
resp: *mut *mut pam_response,
|
|
appdata_ptr: *mut c_void,
|
|
) -> i32 {
|
|
unsafe {
|
|
let appdata = &*(appdata_ptr as *mut PamAppData);
|
|
DEBUG!("pam_conv_handler: called with data {}", appdata.username,);
|
|
let mut replies: Vec<pam_response> = Vec::with_capacity(num_msg as usize);
|
|
|
|
for i in 0..num_msg {
|
|
let m = *msg.add(i as usize);
|
|
let response = match (*m).msg_style {
|
|
PAM_PROMPT_ECHO_OFF => {
|
|
DEBUG!("PAM password request");
|
|
let c_pwd = CString::new(appdata.password.clone()).unwrap();
|
|
pam_response {
|
|
resp: c_pwd.into_raw(),
|
|
resp_retcode: 0,
|
|
}
|
|
}
|
|
PAM_PROMPT_ECHO_ON => {
|
|
let c_user = CString::new(appdata.username.clone()).unwrap();
|
|
pam_response {
|
|
resp: c_user.into_raw(),
|
|
resp_retcode: 0,
|
|
}
|
|
}
|
|
PAM_TEXT_INFO => {
|
|
INFO!(
|
|
"PAM_TEXT_INFO: {}",
|
|
CStr::from_ptr((*m).msg).to_string_lossy().into_owned()
|
|
);
|
|
pam_response {
|
|
resp: ptr::null_mut(),
|
|
resp_retcode: 0,
|
|
}
|
|
}
|
|
PAM_ERROR_MSG => {
|
|
CRITICAL!(
|
|
"PAM_ERROR_MSG: {}",
|
|
CStr::from_ptr((*m).msg).to_string_lossy().into_owned()
|
|
);
|
|
pam_response {
|
|
resp: ptr::null_mut(),
|
|
resp_retcode: 0,
|
|
}
|
|
}
|
|
_ => pam_response {
|
|
resp: ptr::null_mut(),
|
|
resp_retcode: 0,
|
|
},
|
|
};
|
|
|
|
replies.push(response);
|
|
}
|
|
let arr = libc::calloc(replies.len(), size_of::<pam_response>()) as *mut pam_response;
|
|
if arr.is_null() {
|
|
return PAM_BUF_ERR;
|
|
}
|
|
for (i, r) in replies.into_iter().enumerate() {
|
|
*arr.add(i) = r;
|
|
}
|
|
*resp = arr;
|
|
PAM_SUCCESS
|
|
}
|
|
}
|
|
|
|
/// Public trait for all session object
|
|
///
|
|
/// This interface shall implemented for any session object (PamSession, AnonymousSession)
|
|
/// that want to export public API for user authentication and session opening
|
|
///
|
|
pub trait Session {
|
|
/// Perform authentication for user check
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `&self` - Object that implements this trait
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `Result<(), Box<dyn std::error::Error>>`
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Any error during authentication phrase
|
|
///
|
|
fn authenticate(&self) -> Result<(), Box<dyn std::error::Error>>;
|
|
/// Open a new user session after a successful authentication
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `&self` - Object that implement this trait
|
|
/// - `command` (`&str`) - Command to executed in a new user session
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `Result<(), Box<dyn std::error::Error>>`
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Any error raised during session opening
|
|
///
|
|
fn run(&self, command: &str) -> Result<(), Box<dyn std::error::Error>>;
|
|
|
|
/// End the currently running session
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `&self` - Object that implement this trait
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `Result<(), Box<dyn std::error::Error>>`
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Any error raised
|
|
fn end(&self) -> Result<(), Box<dyn std::error::Error>>;
|
|
}
|
|
|
|
/// Trait that provide privilege dropping for new user session
|
|
///
|
|
/// This trait shall be implement on any Session object
|
|
///
|
|
trait DropPrivilege {
|
|
/// Drop the process privilege to current user
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `&self` - Object that define this trait
|
|
/// - `command` (`&str`) - Command that will be executed un the user session after privilege dropping
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `Result<(), Box<dyn std::error::Error>>`
|
|
///
|
|
/// # Errors
|
|
/// Any error
|
|
///
|
|
fn drop_and_run(&self, command: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
DEBUG!("Run command: {} as user: {}", command, self.username());
|
|
let user =
|
|
User::from_name(self.username())?.ok_or(ERR!("Unknown user {}", self.username()))?;
|
|
let username_c = CString::new(self.username())?;
|
|
let username_c_str = username_c.as_c_str();
|
|
initgroups(username_c_str, user.gid)?;
|
|
let groups = getgrouplist(username_c_str, user.gid)?;
|
|
setgid(user.gid)?;
|
|
setgroups(&groups)?;
|
|
setuid(user.uid)?;
|
|
let shell = CString::new("/bin/sh")?;
|
|
let arg0 = CString::new("sh")?;
|
|
let arg_c = CString::new("-c")?;
|
|
let arg_cmd = CString::new(command)?;
|
|
let args = vec![arg0.as_c_str(), arg_c.as_c_str(), arg_cmd.as_c_str()];
|
|
let env_user = CString::new(format!("USER={}", self.username()))?;
|
|
let envs = vec![env_user.as_c_str()];
|
|
execvpe(shell.as_c_str(), &args, &envs)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Getter to get the session username
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `&self` - Object that implements this trait
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `&str` - username
|
|
///
|
|
fn username(&self) -> &str;
|
|
}
|
|
|
|
impl PamSession {
|
|
/// Create a new PamSession object
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `service` (`&str`) - PAM service
|
|
/// - `username` (`&str`) - session user name
|
|
/// - `password` (`&str`) - session user password
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `Result<Self, Box<dyn std::error::Error>>`
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// any error
|
|
pub fn new(
|
|
service: &str,
|
|
username: &str,
|
|
password: &str,
|
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
|
let mut pamh: *mut pam_handle = ptr::null_mut();
|
|
|
|
let c_service = CString::new(service)?;
|
|
let c_user = CString::new(username)?;
|
|
|
|
// allocate user/pass for conv function
|
|
let boxed_data = Box::new(PamAppData {
|
|
username: username.to_string(),
|
|
password: password.to_string(),
|
|
});
|
|
|
|
let appdata_ptr = Box::into_raw(boxed_data);
|
|
|
|
let mut conv = pam_conv {
|
|
conv: pam_conv_handler,
|
|
appdata_ptr: appdata_ptr as *mut _,
|
|
};
|
|
DEBUG!(
|
|
"Open PAM session with service: {} for user {}",
|
|
service,
|
|
username
|
|
);
|
|
let code = unsafe { pam_start(c_service.as_ptr(), c_user.as_ptr(), &mut conv, &mut pamh) };
|
|
if code != PAM_SUCCESS {
|
|
unsafe {
|
|
drop(Box::from_raw(appdata_ptr));
|
|
}
|
|
Err(Self::pam_err(pamh, code))?;
|
|
}
|
|
|
|
Ok(Self {
|
|
pamh,
|
|
appdata: appdata_ptr,
|
|
})
|
|
}
|
|
|
|
/// Utility method that convert low level PAM error to std::io::Error
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `pamh` (`*mut pam_handle`) - PAM handle
|
|
/// - `code` (`i32`) - PAM error code
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `std::io::Error`
|
|
fn pam_err(pamh: *mut pam_handle, code: i32) -> std::io::Error {
|
|
unsafe {
|
|
let msg = CStr::from_ptr(pam_strerror(pamh, code));
|
|
std::io::Error::new(
|
|
std::io::ErrorKind::Other,
|
|
msg.to_string_lossy().into_owned(),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Wrapper for low-level pam_set_item
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `&self` (`PamSession`) - PamSession object
|
|
/// - `flag` (`i32`) - PAM flag
|
|
/// - `value` (`&str`) - value to set
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `Result<&Self, Box<dyn std::error::Error>>`
|
|
///
|
|
/// # Errors
|
|
/// any error
|
|
fn pam_set_item(&self, flag: i32, value: &str) -> Result<&Self, Box<dyn std::error::Error>> {
|
|
let data = CString::new(value)?;
|
|
let ret = unsafe { pam_set_item(self.pamh, flag, data.as_ptr() as *const c_void) };
|
|
if ret != PAM_SUCCESS {
|
|
return Err(Self::pam_err(self.pamh, ret))?;
|
|
}
|
|
Ok(self)
|
|
}
|
|
|
|
/// Wrapper for low level pam_authenticate
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `&self` (`PamSession`) - PamSession object
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `Result<&Self, Box<dyn std::error::Error>>`
|
|
///
|
|
/// # Errors
|
|
/// any error
|
|
fn pam_authenticate(&self) -> Result<&Self, Box<dyn std::error::Error>> {
|
|
let ret = unsafe { pam_authenticate(self.pamh, 0) };
|
|
// remove password from memory
|
|
unsafe {
|
|
let appdata = &mut (*self.appdata);
|
|
appdata.password = "".to_string();
|
|
}
|
|
if ret != PAM_SUCCESS {
|
|
return Err(Self::pam_err(self.pamh, ret))?;
|
|
}
|
|
Ok(self)
|
|
}
|
|
|
|
/// Wrapper for low level pam_acct_mgmt
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `&self` (`PamSession`) - PamSession object
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `Result<&Self, Box<dyn std::error::Error>>`
|
|
///
|
|
/// # Errors
|
|
/// any error
|
|
fn pam_check_account(&self) -> Result<&Self, Box<dyn std::error::Error>> {
|
|
let ret = unsafe { pam_acct_mgmt(self.pamh, 0) };
|
|
if ret != PAM_SUCCESS {
|
|
return Err(Self::pam_err(self.pamh, ret))?;
|
|
}
|
|
Ok(self)
|
|
}
|
|
|
|
/// Wrapper for low level pam_setcred
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `&self` (`PamSession`) - PamSession object
|
|
/// - `flag` (`i32`) - PAM flag
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `Result<&Self, Box<dyn std::error::Error>>`
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// any error
|
|
fn pam_set_credentials(&self, flag: i32) -> Result<&Self, Box<dyn std::error::Error>> {
|
|
let ret = unsafe { pam_setcred(self.pamh, flag) };
|
|
if ret != PAM_SUCCESS {
|
|
return Err(Self::pam_err(self.pamh, ret))?;
|
|
}
|
|
Ok(self)
|
|
}
|
|
|
|
/// Wrapper for low level pam_open_session
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `&self` (`PamSession`) - PamSession object
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `Result<&Self, Box<dyn std::error::Error>>`
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// any error
|
|
fn pam_open_session(&self) -> Result<&Self, Box<dyn std::error::Error>> {
|
|
let ret = unsafe { pam_open_session(self.pamh, 0) };
|
|
if ret != PAM_SUCCESS {
|
|
return Err(Self::pam_err(self.pamh, ret))?;
|
|
}
|
|
Ok(self)
|
|
}
|
|
|
|
/// Wrapper for low level pam_close_session
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `&self` (`PamSession`) - PamSession object
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `Result<&Self, Box<dyn std::error::Error>>`
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// any error
|
|
fn pam_close_session(&self) -> Result<&Self, Box<dyn std::error::Error>> {
|
|
let ret = unsafe { pam_close_session(self.pamh, 0) };
|
|
if ret != PAM_SUCCESS {
|
|
return Err(Self::pam_err(self.pamh, ret))?;
|
|
}
|
|
Ok(self)
|
|
}
|
|
|
|
/// Wrapper for low level pam_end
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `&self` (`PamSession`) - PamSession object
|
|
///
|
|
fn pam_end(&self) {
|
|
unsafe {
|
|
let _ = pam_end(self.pamh, PAM_SUCCESS);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DropPrivilege for PamSession {
|
|
fn username(&self) -> &str {
|
|
let appdata = unsafe { &*(self.appdata as *mut PamAppData) };
|
|
&appdata.username
|
|
}
|
|
}
|
|
|
|
impl Session for AnonymousSession {
|
|
fn authenticate(&self) -> Result<(), Box<dyn std::error::Error>> {
|
|
Ok(())
|
|
}
|
|
|
|
fn run(&self, command: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
self.drop_and_run(command)
|
|
}
|
|
|
|
fn end(&self) -> Result<(), Box<dyn std::error::Error>> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Session for PamSession {
|
|
fn authenticate(&self) -> Result<(), Box<dyn std::error::Error>> {
|
|
if let Ok(var) = std::env::var("DISPLAY") {
|
|
let _ = self.pam_set_item(PAM_XDISPLAY, &var)?;
|
|
}
|
|
if let Ok(var) = std::env::var("TTY") {
|
|
let _ = self.pam_set_item(PAM_TTY, &var)?;
|
|
}
|
|
let _ = self
|
|
.pam_set_item(PAM_USER, self.username())?
|
|
.pam_authenticate()?
|
|
.pam_check_account()?;
|
|
Ok(())
|
|
}
|
|
|
|
fn run(&self, command: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
self.pam_set_credentials(PAM_ESTABLISH_CRED)?
|
|
.pam_open_session()?
|
|
.drop_and_run(command)
|
|
}
|
|
|
|
fn end(&self) -> Result<(), Box<dyn std::error::Error>> {
|
|
let _ = self.pam_close_session();
|
|
let _ = self.pam_set_credentials(PAM_DELETE_CRED);
|
|
Ok(self.pam_end())
|
|
}
|
|
}
|
|
|
|
impl AnonymousSession {
|
|
/// Create new AnonymousSession
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// - `username` (`&str`) - username
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// - `Self`
|
|
///
|
|
pub fn new(username: &str) -> Self {
|
|
AnonymousSession {
|
|
username: username.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DropPrivilege for AnonymousSession {
|
|
fn username(&self) -> &str {
|
|
&self.username
|
|
}
|
|
}
|
|
|
|
impl Drop for PamSession {
|
|
fn drop(&mut self) {
|
|
DEBUG!("Drop PAM session for user {}", self.username());
|
|
if let Err(error) = self.end() {
|
|
WARNING!("Unable to properly close PAM session: {}", error);
|
|
}
|
|
unsafe {
|
|
// free the Box<PamAppData>
|
|
if !self.appdata.is_null() {
|
|
drop(Box::from_raw(self.appdata));
|
|
}
|
|
}
|
|
}
|
|
}
|