use std::{env, ffi::CStr, os::raw::c_char};
use crate::{conv, enums::*, functions::*, types::*};
pub struct Client<'a, C: conv::Conversation> {
pub close_on_drop: bool,
handle: &'a mut PamHandle,
conversation: Box<C>,
is_authenticated: bool,
has_open_session: bool,
last_code: PamReturnCode,
}
impl<'a> Client<'a, conv::PasswordConv> {
pub fn with_password(service: &str) -> PamResult<Client<'a, conv::PasswordConv>> {
Client::with_conversation(service, conv::PasswordConv::new())
}
}
impl<'a, C: conv::Conversation> Client<'a, C> {
pub fn with_conversation(service: &str, conversation: C) -> PamResult<Client<'a, C>> {
let mut conversation = Box::new(conversation);
let conv = conv::into_pam_conv(&mut *conversation);
let handle = start(service, None, &conv)?;
Ok(Client {
close_on_drop: true,
handle,
conversation,
is_authenticated: false,
has_open_session: false,
last_code: PamReturnCode::Success,
})
}
pub fn conversation(&self) -> &C {
&*self.conversation
}
pub fn conversation_mut(&mut self) -> &mut C {
&mut *self.conversation
}
pub fn authenticate(&mut self) -> PamResult<()> {
self.last_code = authenticate(self.handle, PamFlag::None);
if self.last_code != PamReturnCode::Success {
return Err(From::from(self.last_code));
}
self.is_authenticated = true;
self.last_code = acct_mgmt(self.handle, PamFlag::None);
if self.last_code != PamReturnCode::Success {
return self.reset();
}
Ok(())
}
pub fn change_authentication_token(&mut self, flags: PamFlag) -> PamResult<()> {
self.last_code = chauthtok(self.handle, flags);
if self.last_code != PamReturnCode::Success {
return Err(From::from(self.last_code));
}
Ok(())
}
pub fn get_user(&mut self) -> PamResult<String> {
get_item(self.handle, PamItemType::User).and_then(|result| {
let ptr: *const c_char = unsafe { std::mem::transmute(result) };
let username = unsafe { CStr::from_ptr(ptr) };
match username.to_str() {
Err(_) => Err(PamError(PamReturnCode::System_Err)),
Ok(username) => Ok(username.to_string()),
}
})
}
pub fn open_session(&mut self) -> PamResult<()> {
if !self.is_authenticated {
return Err(PamReturnCode::Perm_Denied.into());
}
self.last_code = setcred(self.handle, PamFlag::Establish_Cred);
if self.last_code != PamReturnCode::Success {
return self.reset();
}
self.last_code = open_session(self.handle, false);
if self.last_code != PamReturnCode::Success {
return self.reset();
}
self.last_code = setcred(self.handle, PamFlag::Reinitialize_Cred);
if self.last_code != PamReturnCode::Success {
return self.reset();
}
self.has_open_session = true;
self.initialize_environment()
}
fn initialize_environment(&mut self) -> PamResult<()> {
use users::os::unix::UserExt;
let user = users::get_user_by_name(&self.get_user()?).unwrap_or_else(|| {
panic!(
"Could not get user by name: {:?}",
self.get_user()
)
});
self.set_env(
"USER",
user.name()
.to_str()
.expect("Unix usernames should be valid UTF-8"),
)?;
self.set_env(
"LOGNAME",
user.name()
.to_str()
.expect("Unix usernames should be valid UTF-8"),
)?;
self.set_env("HOME", user.home_dir().to_str().unwrap())?;
self.set_env("PWD", user.home_dir().to_str().unwrap())?;
self.set_env("SHELL", user.shell().to_str().unwrap())?;
Ok(())
}
fn set_env(&mut self, key: &str, value: &str) -> PamResult<()> {
env::set_var(key, value);
if getenv(self.handle, key).is_ok() {
let name_value = format!("{}={}", key, value);
putenv(self.handle, &name_value)
} else {
Ok(())
}
}
fn reset(&mut self) -> PamResult<()> {
setcred(self.handle, PamFlag::Delete_Cred);
self.is_authenticated = false;
Err(From::from(self.last_code))
}
}
impl<'a, C: conv::Conversation> Drop for Client<'a, C> {
fn drop(&mut self) {
if self.has_open_session && self.close_on_drop {
close_session(self.handle, false);
}
let code = setcred(self.handle, PamFlag::Delete_Cred);
end(self.handle, code);
}
}