use ffi;
use pam::{self, PamConversation, PamFlag, PamHandle, PamReturnCode};
use users;
pub struct Authenticator<'a, 'b> {
pub close_on_drop: bool,
handle: &'a mut PamHandle,
credentials: Box<[&'b str; 2]>,
is_authenticated: bool,
has_open_session: bool,
last_code: PamReturnCode,
}
impl<'a, 'b> Authenticator<'a, 'b> {
pub fn new(service: &str) -> Option<Authenticator> {
use std::ptr;
let creds = Box::new([""; 2]);
let conv = PamConversation {
conv: Some(ffi::converse),
data_ptr: creds.as_ptr() as *mut ::libc::c_void,
};
let mut handle: *mut PamHandle = ptr::null_mut();
match pam::start(service, None, &conv, &mut handle) {
PamReturnCode::SUCCESS => unsafe {
Some(Authenticator {
close_on_drop: true,
handle: &mut *handle,
credentials: creds,
is_authenticated: false,
has_open_session: false,
last_code: PamReturnCode::SUCCESS,
})
},
_ => None,
}
}
pub fn set_credentials(&mut self, user: &'b str, password: &'b str) {
self.credentials[0] = user;
self.credentials[1] = password;
}
pub fn authenticate(&mut self) -> ::Result<()> {
self.last_code = pam::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 = pam::acct_mgmt(self.handle, PamFlag::NONE);
if self.last_code != PamReturnCode::SUCCESS {
return self.reset();
}
Ok(())
}
pub fn open_session(&mut self) -> ::Result<()> {
if !self.is_authenticated {
return Err(From::from(PamReturnCode::PERM_DENIED));
}
self.last_code = pam::setcred(self.handle, PamFlag::ESTABLISH_CRED);
if self.last_code != PamReturnCode::SUCCESS {
return self.reset();
}
self.last_code = pam::open_session(self.handle, PamFlag::NONE);
if self.last_code != PamReturnCode::SUCCESS {
return self.reset();
}
self.last_code = pam::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) -> ::Result<()> {
use users::os::unix::UserExt;
use std::env;
if let Some(mut env_list) = ::env::get_pam_env(self.handle) {
let env = env_list.to_vec();
for (key, value) in env {
env::set_var(&key, &value);
}
}
let user = users::get_user_by_name(self.credentials[0])
.expect(&format!("Could not get user by name: {:?}", self.credentials[0]));
try!(self.set_env("USER", user.name()));
try!(self.set_env("LOGNAME", user.name()));
try!(self.set_env("HOME", user.home_dir().to_str().unwrap()));
try!(self.set_env("PWD", user.home_dir().to_str().unwrap()));
try!(self.set_env("SHELL", user.shell().to_str().unwrap()));
try!(self.set_env("PATH", "$PATH:/usr/local/sbin:/usr/local/bin:/usr/bin"));
Ok(())
}
fn set_env(&mut self, key: &str, value: &str) -> ::Result<()> {
use std::env;
env::set_var(key, value);
if pam::getenv(self.handle, key).is_none() {
let name_value = format!("{}={}", key, value);
match pam::putenv(self.handle, &name_value) {
PamReturnCode::SUCCESS => Ok(()),
code => Err(From::from(code)),
}
} else {
Ok(())
}
}
fn reset(&mut self) -> ::Result<()> {
pam::setcred(self.handle, PamFlag::DELETE_CRED);
self.is_authenticated = false;
Err(From::from(self.last_code))
}
}
impl<'a, 'b> Drop for Authenticator<'a, 'b> {
fn drop(&mut self) {
if self.has_open_session && self.close_on_drop {
pam::close_session(self.handle, PamFlag::NONE);
}
let code = pam::setcred(self.handle, PamFlag::DELETE_CRED);
pam::end(self.handle, code);
}
}