use hashbrown::HashSet; use std::collections::BTreeSet; use std::num::NonZeroUsize; use std::ops::{Add, Sub}; use std::path::Path; use std::string::ToString; use std::time::{Duration, SystemTime}; use kanidm_client::{ClientError, KanidmClient}; use kanidm_proto::v1::{OperationError, UnixGroupToken, UnixUserToken}; use lru::LruCache; use reqwest::StatusCode; use tokio::sync::{Mutex, RwLock}; use crate::db::{Cache, CacheTxn, Db}; use crate::unix_config::{HomeAttr, UidAttr}; use crate::unix_proto::{HomeDirectoryInfo, NssGroup, NssUser}; // use crate::unix_passwd::{EtcUser, EtcGroup}; const NXCACHE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(2048) }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Id { Name(String), Gid(u32), } #[derive(Debug, Clone)] enum CacheState { Online, Offline, OfflineNextCheck(SystemTime), } #[derive(Debug)] pub struct Resolver { db: Db, client: RwLock, state: Mutex, pam_allow_groups: BTreeSet, timeout_seconds: u64, default_shell: String, home_prefix: String, home_attr: HomeAttr, home_alias: Option, uid_attr_map: UidAttr, gid_attr_map: UidAttr, allow_id_overrides: HashSet, nxset: Mutex>, nxcache: Mutex>, } impl ToString for Id { fn to_string(&self) -> String { match self { Id::Name(s) => s.clone(), Id::Gid(g) => g.to_string(), } } } impl Resolver { #[allow(clippy::too_many_arguments)] pub async fn new( db: Db, // need db path // path: &str, // tpm_policy: &TpmPolicy, // cache timeout timeout_seconds: u64, // client: KanidmClient, pam_allow_groups: Vec, default_shell: String, home_prefix: String, home_attr: HomeAttr, home_alias: Option, uid_attr_map: UidAttr, gid_attr_map: UidAttr, allow_id_overrides: Vec, ) -> Result { // setup and do a migrate. { let dbtxn = db.write().await; dbtxn.migrate().map_err(|_| ())?; dbtxn.commit().map_err(|_| ())?; } if pam_allow_groups.is_empty() { eprintln!("Will not be able to authorise user logins, pam_allow_groups config is not configured."); } // We assume we are offline at start up, and we mark the next "online check" as // being valid from "now". Ok(Resolver { db, client: RwLock::new(client), state: Mutex::new(CacheState::OfflineNextCheck(SystemTime::now())), timeout_seconds, pam_allow_groups: pam_allow_groups.into_iter().collect(), default_shell, home_prefix, home_attr, home_alias, uid_attr_map, gid_attr_map, allow_id_overrides: allow_id_overrides.into_iter().map(Id::Name).collect(), nxset: Mutex::new(HashSet::new()), nxcache: Mutex::new(LruCache::new(NXCACHE_SIZE)), }) } async fn get_cachestate(&self) -> CacheState { let g = self.state.lock().await; (*g).clone() } async fn set_cachestate(&self, state: CacheState) { let mut g = self.state.lock().await; *g = state; } // Need a way to mark online/offline. pub async fn attempt_online(&self) { self.set_cachestate(CacheState::OfflineNextCheck(SystemTime::now())) .await; } pub async fn mark_offline(&self) { self.set_cachestate(CacheState::Offline).await; } pub async fn clear_cache(&self) -> Result<(), ()> { let mut nxcache_txn = self.nxcache.lock().await; nxcache_txn.clear(); let dbtxn = self.db.write().await; dbtxn.clear().and_then(|_| dbtxn.commit()).map_err(|_| ()) } pub async fn invalidate(&self) -> Result<(), ()> { let mut nxcache_txn = self.nxcache.lock().await; nxcache_txn.clear(); let dbtxn = self.db.write().await; dbtxn .invalidate() .and_then(|_| dbtxn.commit()) .map_err(|_| ()) } async fn get_cached_usertokens(&self) -> Result, ()> { let dbtxn = self.db.write().await; dbtxn.get_accounts().map_err(|_| ()) } async fn get_cached_grouptokens(&self) -> Result, ()> { let dbtxn = self.db.write().await; dbtxn.get_groups().map_err(|_| ()) } async fn set_nxcache(&self, id: &Id) { let mut nxcache_txn = self.nxcache.lock().await; let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds); nxcache_txn.put(id.clone(), ex_time); } pub async fn check_nxcache(&self, id: &Id) -> Option { let mut nxcache_txn = self.nxcache.lock().await; nxcache_txn.get(id).copied() } pub async fn reload_nxset(&self, iter: impl Iterator) { let mut nxset_txn = self.nxset.lock().await; nxset_txn.clear(); for (name, gid) in iter { let name = Id::Name(name); let gid = Id::Gid(gid); // Skip anything that the admin opted in to if !(self.allow_id_overrides.contains(&gid) || self.allow_id_overrides.contains(&name)) { debug!("Adding {:?}:{:?} to resolver exclusion set", name, gid); nxset_txn.insert(name); nxset_txn.insert(gid); } } } pub async fn check_nxset(&self, name: &str, idnumber: u32) -> bool { let nxset_txn = self.nxset.lock().await; nxset_txn.contains(&Id::Gid(idnumber)) || nxset_txn.contains(&Id::Name(name.to_string())) } async fn get_cached_usertoken( &self, account_id: &Id, ) -> Result<(bool, Option), ()> { // Account_id could be: // * gidnumber // * name // * spn // * uuid // Attempt to search these in the db. let dbtxn = self.db.write().await; let r = dbtxn.get_account(account_id).map_err(|_| ())?; match r { Some((ut, ex)) => { // Are we expired? let offset = Duration::from_secs(ex); let ex_time = SystemTime::UNIX_EPOCH + offset; let now = SystemTime::now(); if now >= ex_time { Ok((true, Some(ut))) } else { Ok((false, Some(ut))) } } None => { // it wasn't in the DB - lets see if it's in the nxcache. match self.check_nxcache(account_id).await { Some(ex_time) => { let now = SystemTime::now(); if now >= ex_time { // It's in the LRU, but we are past the expiry so // lets attempt a refresh. Ok((true, None)) } else { // It's in the LRU and still valid, so return that // no check is needed. Ok((false, None)) } } None => { // Not in the LRU. Return that this IS expired // and we have no data. Ok((true, None)) } } } } // end match r } async fn get_cached_grouptoken( &self, grp_id: &Id, ) -> Result<(bool, Option), ()> { // grp_id could be: // * gidnumber // * name // * spn // * uuid // Attempt to search these in the db. let dbtxn = self.db.write().await; let r = dbtxn.get_group(grp_id).map_err(|_| ())?; match r { Some((ut, ex)) => { // Are we expired? let offset = Duration::from_secs(ex); let ex_time = SystemTime::UNIX_EPOCH + offset; let now = SystemTime::now(); if now >= ex_time { Ok((true, Some(ut))) } else { Ok((false, Some(ut))) } } None => { // it wasn't in the DB - lets see if it's in the nxcache. match self.check_nxcache(grp_id).await { Some(ex_time) => { let now = SystemTime::now(); if now >= ex_time { // It's in the LRU, but we are past the expiry so // lets attempt a refresh. Ok((true, None)) } else { // It's in the LRU and still valid, so return that // no check is needed. Ok((false, None)) } } None => { // Not in the LRU. Return that this IS expired // and we have no data. Ok((true, None)) } } } } } async fn set_cache_usertoken(&self, token: &mut UnixUserToken) -> Result<(), ()> { // Set an expiry let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds); let offset = ex_time .duration_since(SystemTime::UNIX_EPOCH) .map_err(|e| { error!("time conversion error - ex_time less than epoch? {:?}", e); })?; // Check if requested `shell` exists on the system, else use `default_shell` let requested_shell_exists: bool = token .shell .as_ref() .map(|shell| { let exists = Path::new(shell).exists(); if !exists { info!( "User requested shell is not present on this system - {}", shell ) } exists }) .unwrap_or_else(|| { info!("User has not specified a shell, using default"); false }); if !requested_shell_exists { token.shell = Some(self.default_shell.clone()) } // Filter out groups that are in the nxset { let nxset_txn = self.nxset.lock().await; token.groups.retain(|g| { !(nxset_txn.contains(&Id::Gid(g.gidnumber)) || nxset_txn.contains(&Id::Name(g.name.clone()))) }); } let dbtxn = self.db.write().await; token .groups .iter() // We need to add the groups first .try_for_each(|g| dbtxn.update_group(g, offset.as_secs())) .and_then(|_| // So that when we add the account it can make the relationships. dbtxn .update_account(token, offset.as_secs())) .and_then(|_| dbtxn.commit()) .map_err(|_| ()) } async fn set_cache_grouptoken(&self, token: &UnixGroupToken) -> Result<(), ()> { // Set an expiry let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds); let offset = ex_time .duration_since(SystemTime::UNIX_EPOCH) .map_err(|e| { error!("time conversion error - ex_time less than epoch? {:?}", e); })?; let dbtxn = self.db.write().await; dbtxn .update_group(token, offset.as_secs()) .and_then(|_| dbtxn.commit()) .map_err(|_| ()) } async fn delete_cache_usertoken(&self, a_uuid: &str) -> Result<(), ()> { let dbtxn = self.db.write().await; dbtxn .delete_account(a_uuid) .and_then(|_| dbtxn.commit()) .map_err(|_| ()) } async fn delete_cache_grouptoken(&self, g_uuid: &str) -> Result<(), ()> { let dbtxn = self.db.write().await; dbtxn .delete_group(g_uuid) .and_then(|_| dbtxn.commit()) .map_err(|_| ()) } async fn set_cache_userpassword(&self, a_uuid: &str, cred: &str) -> Result<(), ()> { let dbtxn = self.db.write().await; dbtxn .update_account_password(a_uuid, cred) .and_then(|x| dbtxn.commit().map(|_| x)) .map_err(|_| ()) } async fn check_cache_userpassword(&self, a_uuid: &str, cred: &str) -> Result { let dbtxn = self.db.write().await; dbtxn .check_account_password(a_uuid, cred) .and_then(|x| dbtxn.commit().map(|_| x)) .map_err(|_| ()) } async fn refresh_usertoken( &self, account_id: &Id, token: Option, ) -> Result, ()> { match self .client .read() .await .idm_account_unix_token_get(account_id.to_string().as_str()) .await { Ok(mut n_tok) => { if self.check_nxset(&n_tok.name, n_tok.gidnumber).await { // Refuse to release the token, it's in the denied set. self.delete_cache_usertoken(&n_tok.uuid).await?; Ok(None) } else { // We have the token! self.set_cache_usertoken(&mut n_tok).await?; Ok(Some(n_tok)) } } Err(e) => { match e { ClientError::Transport(er) => { error!("transport error, moving to offline -> {:?}", er); // Something went wrong, mark offline. let time = SystemTime::now().add(Duration::from_secs(15)); self.set_cachestate(CacheState::OfflineNextCheck(time)) .await; Ok(token) } ClientError::Http(StatusCode::UNAUTHORIZED, reason, opid) => { match reason { Some(OperationError::NotAuthenticated) => warn!("session not authenticated - attempting reauthentication - eventid {}", opid), Some(OperationError::SessionExpired) => warn!("session expired - attempting reauthentication - eventid {}", opid), e => error!( "authentication error {:?}, moving to offline - eventid {}", e, opid ), }; // Something went wrong, mark offline to force a re-auth ASAP. let time = SystemTime::now().sub(Duration::from_secs(1)); self.set_cachestate(CacheState::OfflineNextCheck(time)) .await; Ok(token) } ClientError::Http( StatusCode::BAD_REQUEST, Some(OperationError::NoMatchingEntries), opid, ) | ClientError::Http( StatusCode::NOT_FOUND, Some(OperationError::NoMatchingEntries), opid, ) | ClientError::Http( StatusCode::BAD_REQUEST, Some(OperationError::InvalidAccountState(_)), opid, ) => { // We wele able to contact the server but the entry has been removed, or // is not longer a valid posix account. debug!("entry has been removed or is no longer a valid posix account, clearing from cache ... - eventid {}", opid); if let Some(tok) = token { self.delete_cache_usertoken(&tok.uuid).await?; }; // Cache the NX here. self.set_nxcache(account_id).await; Ok(None) } er => { error!("client error -> {:?}", er); // Some other transient error, continue with the token. Ok(token) } } } } } async fn refresh_grouptoken( &self, grp_id: &Id, token: Option, ) -> Result, ()> { match self .client .read() .await .idm_group_unix_token_get(grp_id.to_string().as_str()) .await { Ok(n_tok) => { if self.check_nxset(&n_tok.name, n_tok.gidnumber).await { // Refuse to release the token, it's in the denied set. self.delete_cache_grouptoken(&n_tok.uuid).await?; Ok(None) } else { // We have the token! self.set_cache_grouptoken(&n_tok).await?; Ok(Some(n_tok)) } } Err(e) => { match e { ClientError::Transport(er) => { error!("transport error, moving to offline -> {:?}", er); // Something went wrong, mark offline. let time = SystemTime::now().add(Duration::from_secs(15)); self.set_cachestate(CacheState::OfflineNextCheck(time)) .await; Ok(token) } ClientError::Http( StatusCode::UNAUTHORIZED, Some(OperationError::NotAuthenticated), opid, ) => { error!( "transport unauthenticated, moving to offline - eventid {}", opid ); // Something went wrong, mark offline. let time = SystemTime::now().add(Duration::from_secs(15)); self.set_cachestate(CacheState::OfflineNextCheck(time)) .await; Ok(token) } ClientError::Http( StatusCode::BAD_REQUEST, Some(OperationError::NoMatchingEntries), opid, ) | ClientError::Http( StatusCode::NOT_FOUND, Some(OperationError::NoMatchingEntries), opid, ) | ClientError::Http( StatusCode::BAD_REQUEST, Some(OperationError::InvalidAccountState(_)), opid, ) => { debug!("entry has been removed or is no longer a valid posix group, clearing from cache ... - eventid {}", opid); if let Some(tok) = token { self.delete_cache_grouptoken(&tok.uuid).await?; }; // Cache the NX here. self.set_nxcache(grp_id).await; Ok(None) } er => { error!("client error -> {:?}", er); // Some other transient error, continue with the token. Ok(token) } } } } } async fn get_usertoken(&self, account_id: Id) -> Result, ()> { debug!("get_usertoken"); // get the item from the cache let (expired, item) = self.get_cached_usertoken(&account_id).await.map_err(|e| { debug!("get_usertoken error -> {:?}", e); })?; let state = self.get_cachestate().await; match (expired, state) { (_, CacheState::Offline) => { debug!("offline, returning cached item"); Ok(item) } (false, CacheState::OfflineNextCheck(time)) => { debug!( "offline valid, next check {:?}, returning cached item", time ); // Still valid within lifetime, return. Ok(item) } (false, CacheState::Online) => { debug!("online valid, returning cached item"); // Still valid within lifetime, return. Ok(item) } (true, CacheState::OfflineNextCheck(time)) => { debug!("offline expired, next check {:?}, refresh cache", time); // Attempt to refresh the item // Return it. if SystemTime::now() >= time && self.test_connection().await { // We brought ourselves online, lets go self.refresh_usertoken(&account_id, item).await } else { // Unable to bring up connection, return cache. Ok(item) } } (true, CacheState::Online) => { debug!("online expired, refresh cache"); // Attempt to refresh the item // Return it. self.refresh_usertoken(&account_id, item).await } } .map(|t| { debug!("token -> {:?}", t); t }) } async fn get_grouptoken(&self, grp_id: Id) -> Result, ()> { debug!("get_grouptoken"); let (expired, item) = self.get_cached_grouptoken(&grp_id).await.map_err(|e| { debug!("get_grouptoken error -> {:?}", e); })?; let state = self.get_cachestate().await; match (expired, state) { (_, CacheState::Offline) => { debug!("offline, returning cached item"); Ok(item) } (false, CacheState::OfflineNextCheck(time)) => { debug!( "offline valid, next check {:?}, returning cached item", time ); // Still valid within lifetime, return. Ok(item) } (false, CacheState::Online) => { debug!("online valid, returning cached item"); // Still valid within lifetime, return. Ok(item) } (true, CacheState::OfflineNextCheck(time)) => { debug!("offline expired, next check {:?}, refresh cache", time); // Attempt to refresh the item // Return it. if SystemTime::now() >= time && self.test_connection().await { // We brought ourselves online, lets go self.refresh_grouptoken(&grp_id, item).await } else { // Unable to bring up connection, return cache. Ok(item) } } (true, CacheState::Online) => { debug!("online expired, refresh cache"); // Attempt to refresh the item // Return it. self.refresh_grouptoken(&grp_id, item).await } } } async fn get_groupmembers(&self, uuid: &str) -> Vec { let dbtxn = self.db.write().await; dbtxn .get_group_members(uuid) .unwrap_or_else(|_| Vec::new()) .into_iter() .map(|ut| self.token_uidattr(&ut)) .collect() } // Get ssh keys for an account id pub async fn get_sshkeys(&self, account_id: &str) -> Result, ()> { let token = self.get_usertoken(Id::Name(account_id.to_string())).await?; Ok(token .map(|t| { // Only return keys if the account is valid if t.valid { t.sshkeys } else { Vec::with_capacity(0) } }) .unwrap_or_else(|| Vec::with_capacity(0))) } #[inline(always)] fn token_homedirectory_alias(&self, token: &UnixUserToken) -> Option { self.home_alias.map(|t| match t { // If we have an alias. use it. HomeAttr::Uuid => token.uuid.as_str().to_string(), HomeAttr::Spn => token.spn.as_str().to_string(), HomeAttr::Name => token.name.as_str().to_string(), }) } #[inline(always)] fn token_homedirectory_attr(&self, token: &UnixUserToken) -> String { match self.home_attr { HomeAttr::Uuid => token.uuid.as_str().to_string(), HomeAttr::Spn => token.spn.as_str().to_string(), HomeAttr::Name => token.name.as_str().to_string(), } } #[inline(always)] fn token_homedirectory(&self, token: &UnixUserToken) -> String { self.token_homedirectory_alias(token) .unwrap_or_else(|| self.token_homedirectory_attr(token)) } #[inline(always)] fn token_abs_homedirectory(&self, token: &UnixUserToken) -> String { format!("{}{}", self.home_prefix, self.token_homedirectory(token)) } #[inline(always)] fn token_uidattr(&self, token: &UnixUserToken) -> String { match self.uid_attr_map { UidAttr::Spn => token.spn.as_str(), UidAttr::Name => token.name.as_str(), } .to_string() } pub async fn get_nssaccounts(&self) -> Result, ()> { self.get_cached_usertokens().await.map(|l| { l.into_iter() .map(|tok| NssUser { homedir: self.token_abs_homedirectory(&tok), name: self.token_uidattr(&tok), gid: tok.gidnumber, gecos: tok.displayname, shell: tok.shell.unwrap_or_else(|| self.default_shell.clone()), }) .collect() }) } async fn get_nssaccount(&self, account_id: Id) -> Result, ()> { let token = self.get_usertoken(account_id).await?; Ok(token.map(|tok| NssUser { homedir: self.token_abs_homedirectory(&tok), name: self.token_uidattr(&tok), gid: tok.gidnumber, gecos: tok.displayname, shell: tok.shell.unwrap_or_else(|| self.default_shell.clone()), })) } pub async fn get_nssaccount_name(&self, account_id: &str) -> Result, ()> { self.get_nssaccount(Id::Name(account_id.to_string())).await } pub async fn get_nssaccount_gid(&self, gid: u32) -> Result, ()> { self.get_nssaccount(Id::Gid(gid)).await } #[inline(always)] fn token_gidattr(&self, token: &UnixGroupToken) -> String { match self.gid_attr_map { UidAttr::Spn => token.spn.as_str(), UidAttr::Name => token.name.as_str(), } .to_string() } pub async fn get_nssgroups(&self) -> Result, ()> { let l = self.get_cached_grouptokens().await?; let mut r: Vec<_> = Vec::with_capacity(l.len()); for tok in l.into_iter() { let members = self.get_groupmembers(&tok.uuid).await; r.push(NssGroup { name: self.token_gidattr(&tok), gid: tok.gidnumber, members, }) } Ok(r) } async fn get_nssgroup(&self, grp_id: Id) -> Result, ()> { let token = self.get_grouptoken(grp_id).await?; // Get members set. match token { Some(tok) => { let members = self.get_groupmembers(&tok.uuid).await; Ok(Some(NssGroup { name: self.token_gidattr(&tok), gid: tok.gidnumber, members, })) } None => Ok(None), } } pub async fn get_nssgroup_name(&self, grp_id: &str) -> Result, ()> { self.get_nssgroup(Id::Name(grp_id.to_string())).await } pub async fn get_nssgroup_gid(&self, gid: u32) -> Result, ()> { self.get_nssgroup(Id::Gid(gid)).await } async fn online_account_authenticate( &self, token: &Option, account_id: &str, cred: &str, ) -> Result, ()> { debug!("Attempt online password check"); // We are online, attempt the pw to the server. match self .client .read() .await .idm_account_unix_cred_verify(account_id, cred) .await { Ok(Some(mut n_tok)) => { if self.check_nxset(&n_tok.name, n_tok.gidnumber).await { // Refuse to release the token, it's in the denied set. self.delete_cache_usertoken(&n_tok.uuid).await?; Ok(None) } else { debug!("online password check success."); self.set_cache_usertoken(&mut n_tok).await?; self.set_cache_userpassword(&n_tok.uuid, cred).await?; Ok(Some(true)) } } Ok(None) => { error!("incorrect password"); // PW failed the check. Ok(Some(false)) } Err(e) => { match e { ClientError::Transport(er) => { error!("transport error, moving to offline -> {:?}", er); // Something went wrong, mark offline. let time = SystemTime::now().add(Duration::from_secs(15)); self.set_cachestate(CacheState::OfflineNextCheck(time)) .await; match token.as_ref() { Some(t) => self.check_cache_userpassword(&t.uuid, cred).await.map(Some), None => Ok(None), } } ClientError::Http(StatusCode::UNAUTHORIZED, reason, opid) => { match reason { Some(OperationError::NotAuthenticated) => warn!("session not authenticated - attempting reauthentication - eventid {}", opid), Some(OperationError::SessionExpired) => warn!("session expired - attempting reauthentication - eventid {}", opid), e => error!( "authentication error {:?}, moving to offline - eventid {}", e, opid ), }; // Something went wrong, mark offline to force a re-auth ASAP. let time = SystemTime::now().sub(Duration::from_secs(1)); self.set_cachestate(CacheState::OfflineNextCheck(time)) .await; match token.as_ref() { Some(t) => self.check_cache_userpassword(&t.uuid, cred).await.map(Some), None => Ok(None), } } ClientError::Http( StatusCode::BAD_REQUEST, Some(OperationError::NoMatchingEntries), opid, ) | ClientError::Http( StatusCode::NOT_FOUND, Some(OperationError::NoMatchingEntries), opid, ) | ClientError::Http( StatusCode::BAD_REQUEST, Some(OperationError::InvalidAccountState(_)), opid, ) => { error!( "unknown account or is not a valid posix account - eventid {}", opid ); Ok(None) } er => { error!("client error -> {:?}", er); // Some other unknown processing error? Err(()) } } } } } async fn offline_account_authenticate( &self, token: &Option, cred: &str, ) -> Result, ()> { debug!("Attempt offline password check"); match token.as_ref() { Some(t) => { if t.valid { self.check_cache_userpassword(&t.uuid, cred).await.map(Some) } else { Ok(Some(false)) } } None => Ok(None), } /* token .as_ref() .map(async |t| self.check_cache_userpassword(&t.uuid, cred).await) .transpose() */ } pub async fn pam_account_allowed(&self, account_id: &str) -> Result, ()> { let token = self.get_usertoken(Id::Name(account_id.to_string())).await?; if self.pam_allow_groups.is_empty() { // can't allow anything if the group list is zero... eprintln!("Cannot authenticate users, no allowed groups in configuration!"); Ok(Some(false)) } else { Ok(token.map(|tok| { let user_set: BTreeSet<_> = tok .groups .iter() .flat_map(|g| [g.name.clone(), g.spn.clone(), g.uuid.clone()]) .collect(); debug!( "Checking if user is in allowed groups ({:?}) -> {:?}", self.pam_allow_groups, user_set, ); let intersection_count = user_set.intersection(&self.pam_allow_groups).count(); debug!("Number of intersecting groups: {}", intersection_count); debug!("User has valid token: {}", tok.valid); intersection_count > 0 && tok.valid })) } } pub async fn pam_account_authenticate( &self, account_id: &str, cred: &str, ) -> Result, ()> { let state = self.get_cachestate().await; let (_expired, token) = self .get_cached_usertoken(&Id::Name(account_id.to_string())) .await?; match state { CacheState::Online => { self.online_account_authenticate(&token, account_id, cred) .await } CacheState::OfflineNextCheck(_time) => { // Always attempt to go online to attempt the authentication. if self.test_connection().await { // Brought ourselves online, lets check. self.online_account_authenticate(&token, account_id, cred) .await } else { // We are offline, check from the cache if possible. self.offline_account_authenticate(&token, cred).await } } _ => { // We are offline, check from the cache if possible. self.offline_account_authenticate(&token, cred).await } } } pub async fn pam_account_beginsession( &self, account_id: &str, ) -> Result, ()> { let token = self.get_usertoken(Id::Name(account_id.to_string())).await?; Ok(token.as_ref().map(|tok| HomeDirectoryInfo { gid: tok.gidnumber, name: self.token_homedirectory_attr(tok), aliases: self .token_homedirectory_alias(tok) .map(|s| vec![s]) .unwrap_or_else(Vec::new), })) } pub async fn test_connection(&self) -> bool { let state = self.get_cachestate().await; match state { CacheState::Offline => { debug!("Offline -> no change"); false } CacheState::OfflineNextCheck(_time) => { match self.client.write().await.auth_anonymous().await { Ok(_uat) => { debug!("OfflineNextCheck -> authenticated"); self.set_cachestate(CacheState::Online).await; true } Err(e) => { debug!("OfflineNextCheck -> disconnected, staying offline. {:?}", e); let time = SystemTime::now().add(Duration::from_secs(15)); self.set_cachestate(CacheState::OfflineNextCheck(time)) .await; false } } } CacheState::Online => { debug!("Online, no change"); true } } } }