kanidm_unix_resolver/
resolver.rs

1// use async_trait::async_trait;
2use crate::db::{Cache, Db};
3use crate::idprovider::interface::{
4    AuthCredHandler,
5    AuthResult,
6    GroupToken,
7    GroupTokenState,
8    Id,
9    IdProvider,
10    IdpError,
11    ProviderOrigin,
12    // KeyStore,
13    UserToken,
14    UserTokenState,
15};
16use crate::idprovider::system::{
17    Shadow, SystemAuthResult, SystemProvider, SystemProviderAuthInit, SystemProviderSession,
18};
19use hashbrown::HashMap;
20use kanidm_unix_common::constants::DEFAULT_SHELL_SEARCH_PATHS;
21use kanidm_unix_common::unix_config::{HomeAttr, UidAttr};
22use kanidm_unix_common::unix_passwd::{EtcGroup, EtcShadow, EtcUser};
23use kanidm_unix_common::unix_proto::{
24    HomeDirectoryInfo, NssGroup, NssUser, PamAuthRequest, PamAuthResponse, PamServiceInfo,
25    ProviderStatus,
26};
27use lru::LruCache;
28use std::fmt::Display;
29use std::num::NonZeroUsize;
30use std::ops::DerefMut;
31use std::path::{Path, PathBuf};
32use std::string::ToString;
33use std::sync::Arc;
34use std::time::{Duration, SystemTime};
35use time::OffsetDateTime;
36use tokio::sync::Mutex;
37use uuid::Uuid;
38
39use kanidm_hsm_crypto::BoxedDynTpm;
40
41use tokio::sync::broadcast;
42
43const NXCACHE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(128) };
44
45pub enum AuthSession {
46    Online {
47        client: Arc<dyn IdProvider + Sync + Send>,
48        account_id: String,
49        id: Id,
50        cred_handler: AuthCredHandler,
51        /// Some authentication operations may need to spawn background tasks. These tasks need
52        /// to know when to stop as the caller has disconnected. This receiver allows that, so
53        /// that tasks which .resubscribe() to this channel can then select! on it and be notified
54        /// when they need to stop.
55        shutdown_rx: broadcast::Receiver<()>,
56    },
57    Offline {
58        account_id: String,
59        id: Id,
60        client: Arc<dyn IdProvider + Sync + Send>,
61        session_token: Box<UserToken>,
62        cred_handler: AuthCredHandler,
63    },
64    System {
65        account_id: String,
66        id: Id,
67        cred_handler: AuthCredHandler,
68        shadow: Arc<Shadow>,
69    },
70    Success,
71    Denied,
72}
73
74pub struct Resolver {
75    // Generic / modular types.
76    db: Db,
77    hsm: Mutex<BoxedDynTpm>,
78
79    // A local passwd/shadow resolver.
80    system_provider: Arc<SystemProvider>,
81
82    // client: Box<dyn IdProvider + Sync + Send>,
83    client_ids: HashMap<ProviderOrigin, Arc<dyn IdProvider + Sync + Send>>,
84
85    // A set of remote resolvers, ordered by priority.
86    clients: Vec<Arc<dyn IdProvider + Sync + Send>>,
87
88    // The id of the primary-provider which may use name over spn.
89    primary_origin: ProviderOrigin,
90
91    timeout_seconds: u64,
92    default_shell: String,
93    home_prefix: PathBuf,
94    home_attr: HomeAttr,
95    home_alias: Option<HomeAttr>,
96    uid_attr_map: UidAttr,
97    gid_attr_map: UidAttr,
98    nxcache: Mutex<LruCache<Id, SystemTime>>,
99}
100
101impl Display for Id {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        f.write_str(&match self {
104            Id::Name(s) => s.to_string(),
105            Id::Gid(g) => g.to_string(),
106        })
107    }
108}
109
110impl Resolver {
111    #[allow(clippy::too_many_arguments)]
112    pub async fn new(
113        db: Db,
114        system_provider: Arc<SystemProvider>,
115        clients: Vec<Arc<dyn IdProvider + Sync + Send>>,
116        hsm: BoxedDynTpm,
117        timeout_seconds: u64,
118        default_shell: String,
119        home_prefix: PathBuf,
120        home_attr: HomeAttr,
121        home_alias: Option<HomeAttr>,
122        uid_attr_map: UidAttr,
123        gid_attr_map: UidAttr,
124    ) -> Result<Self, ()> {
125        let hsm = Mutex::new(hsm);
126
127        let primary_origin = clients.first().map(|c| c.origin()).unwrap_or_default();
128
129        let client_ids: HashMap<_, _> = clients
130            .iter()
131            .map(|provider| (provider.origin(), provider.clone()))
132            .collect();
133
134        // We assume we are offline at start up, and we mark the next "online check" as
135        // being valid from "now".
136        Ok(Resolver {
137            db,
138            hsm,
139            system_provider,
140            clients,
141            primary_origin,
142            client_ids,
143            timeout_seconds,
144            default_shell,
145            home_prefix,
146            home_attr,
147            home_alias,
148            uid_attr_map,
149            gid_attr_map,
150            nxcache: Mutex::new(LruCache::new(NXCACHE_SIZE)),
151        })
152    }
153
154    #[instrument(level = "debug", skip_all)]
155    pub async fn mark_next_check_now(&self, now: SystemTime) {
156        for c in self.clients.iter() {
157            c.mark_next_check(now).await;
158        }
159    }
160
161    #[instrument(level = "debug", skip_all)]
162    pub async fn mark_offline(&self) {
163        for c in self.clients.iter() {
164            c.mark_offline().await;
165        }
166    }
167
168    #[instrument(level = "debug", skip_all)]
169    pub async fn clear_cache(&self) -> Result<(), ()> {
170        let mut dbtxn = self.db.write().await;
171        let mut nxcache_txn = self.nxcache.lock().await;
172        nxcache_txn.clear();
173        dbtxn.clear().and_then(|_| dbtxn.commit()).map_err(|_| ())
174    }
175
176    #[instrument(level = "debug", skip_all)]
177    pub async fn invalidate(&self) -> Result<(), ()> {
178        let mut dbtxn = self.db.write().await;
179        let mut nxcache_txn = self.nxcache.lock().await;
180        nxcache_txn.clear();
181        dbtxn
182            .invalidate()
183            .and_then(|_| dbtxn.commit())
184            .map_err(|_| ())
185    }
186
187    async fn get_cached_usertokens(&self) -> Result<Vec<UserToken>, ()> {
188        let mut dbtxn = self.db.write().await;
189        dbtxn.get_accounts().map_err(|_| ())
190    }
191
192    async fn get_cached_grouptokens(&self) -> Result<Vec<GroupToken>, ()> {
193        let mut dbtxn = self.db.write().await;
194        dbtxn.get_groups().map_err(|_| ())
195    }
196
197    async fn set_nxcache(&self, id: &Id) {
198        let mut nxcache_txn = self.nxcache.lock().await;
199        let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds);
200        nxcache_txn.put(id.clone(), ex_time);
201    }
202
203    pub async fn check_nxcache(&self, id: &Id) -> Option<SystemTime> {
204        let mut nxcache_txn = self.nxcache.lock().await;
205        nxcache_txn.get(id).copied()
206    }
207
208    #[instrument(level = "info", skip_all)]
209    pub async fn reload_system_identities(
210        &self,
211        users: Vec<EtcUser>,
212        shadow: Vec<EtcShadow>,
213        groups: Vec<EtcGroup>,
214    ) {
215        self.system_provider.reload(users, shadow, groups).await
216    }
217
218    async fn get_cached_usertoken(&self, account_id: &Id) -> Result<(bool, Option<UserToken>), ()> {
219        // Account_id could be:
220        //  * gidnumber
221        //  * name
222        //  * spn
223        //  * uuid
224        //  Attempt to search these in the db.
225        let mut dbtxn = self.db.write().await;
226        let r = dbtxn.get_account(account_id).map_err(|err| {
227            debug!(?err, "get_cached_usertoken");
228        })?;
229
230        drop(dbtxn);
231
232        match r {
233            Some((ut, ex)) => {
234                // Are we expired?
235                let offset = Duration::from_secs(ex);
236                let ex_time = SystemTime::UNIX_EPOCH + offset;
237                let now = SystemTime::now();
238
239                if now >= ex_time {
240                    Ok((true, Some(ut)))
241                } else {
242                    Ok((false, Some(ut)))
243                }
244            }
245            None => {
246                // it wasn't in the DB - lets see if it's in the nxcache.
247                match self.check_nxcache(account_id).await {
248                    Some(ex_time) => {
249                        let now = SystemTime::now();
250                        if now >= ex_time {
251                            // It's in the LRU, but we are past the expiry so
252                            // lets attempt a refresh.
253                            Ok((true, None))
254                        } else {
255                            // It's in the LRU and still valid, so return that
256                            // no check is needed.
257                            Ok((false, None))
258                        }
259                    }
260                    None => {
261                        // Not in the LRU. Return that this IS expired
262                        // and we have no data.
263                        Ok((true, None))
264                    }
265                }
266            }
267        } // end match r
268    }
269
270    async fn get_cached_grouptoken(&self, grp_id: &Id) -> Result<(bool, Option<GroupToken>), ()> {
271        // grp_id could be:
272        //  * gidnumber
273        //  * name
274        //  * spn
275        //  * uuid
276        //  Attempt to search these in the db.
277        let mut dbtxn = self.db.write().await;
278        let r = dbtxn.get_group(grp_id).map_err(|_| ())?;
279
280        drop(dbtxn);
281
282        match r {
283            Some((ut, ex)) => {
284                // Are we expired?
285                let offset = Duration::from_secs(ex);
286                let ex_time = SystemTime::UNIX_EPOCH + offset;
287                let now = SystemTime::now();
288
289                if now >= ex_time {
290                    Ok((true, Some(ut)))
291                } else {
292                    Ok((false, Some(ut)))
293                }
294            }
295            None => {
296                // it wasn't in the DB - lets see if it's in the nxcache.
297                match self.check_nxcache(grp_id).await {
298                    Some(ex_time) => {
299                        let now = SystemTime::now();
300                        if now >= ex_time {
301                            // It's in the LRU, but we are past the expiry so
302                            // lets attempt a refresh.
303                            Ok((true, None))
304                        } else {
305                            // It's in the LRU and still valid, so return that
306                            // no check is needed.
307                            Ok((false, None))
308                        }
309                    }
310                    None => {
311                        // Not in the LRU. Return that this IS expired
312                        // and we have no data.
313                        Ok((true, None))
314                    }
315                }
316            }
317        }
318    }
319
320    async fn set_cache_usertoken(
321        &self,
322        token: &mut UserToken,
323        // This is just for proof that only one write can occur at a time.
324        _tpm: &mut BoxedDynTpm,
325    ) -> Result<(), ()> {
326        // Set an expiry
327        let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds);
328        let offset = ex_time
329            .duration_since(SystemTime::UNIX_EPOCH)
330            .map_err(|e| {
331                error!(
332                    "Time conversion error - cache expiry time became less than epoch? {:?}",
333                    e
334                );
335            })?;
336
337        // Check if requested `shell` exists on the system, else use `default_shell`
338        let maybe_shell = token.shell.as_ref().map(PathBuf::from);
339
340        let requested_shell_exists = if let Some(shell_path) = maybe_shell.as_ref() {
341            // Does the shell path as configured exist?
342            let mut exists = shell_path
343                .canonicalize()
344                .map_err(|err| {
345                    debug!(
346                        "Failed to canonicalize path, using base path. Tried: {} Error: {:?}",
347                        shell_path.to_string_lossy(),
348                        err
349                    );
350                })
351                .unwrap_or(Path::new(shell_path).to_path_buf())
352                .exists();
353
354            if !exists {
355                // Does the shell binary exist in a search path that is configured?
356                if let Some(shell_binary_name) = shell_path.file_name() {
357                    for search_path in DEFAULT_SHELL_SEARCH_PATHS {
358                        //
359                        let shell_path = Path::new(search_path).join(shell_binary_name);
360                        if shell_path.exists() {
361                            // Okay, the binary name exists but in an alternate path. This can
362                            // commonly occur with freebsd where the shell may be installed
363                            // in /usr/local/bin instead of /bin.
364                            //
365                            // This could also occur if the user configured the shell as "zsh"
366                            // rather than an absolute path.
367                            let Some(shell_path_utf8) = shell_path.to_str().map(String::from)
368                            else {
369                                warn!("Configured shell \"{}\" for {} was found but the complete path is not valid utf-8 and can not be used.",
370                                        shell_binary_name.to_string_lossy(), token.name);
371                                continue;
372                            };
373
374                            // Update the path
375                            token.shell = Some(shell_path_utf8);
376                            // We exist
377                            exists = true;
378                            // No need to loop any more
379                            break;
380                        }
381                    }
382                }
383            }
384
385            if !exists {
386                warn!(
387                        "Configured shell \"{}\" for {} is not present on this system. Check `/etc/shells` for valid shell options.",
388                        shell_path.to_string_lossy(), token.name
389                    )
390            }
391
392            exists
393        } else {
394            info!("User has not specified a shell, using default");
395            false
396        };
397
398        if !requested_shell_exists {
399            token.shell = Some(self.default_shell.clone())
400        }
401
402        let mut dbtxn = self.db.write().await;
403        token
404            .groups
405            .iter()
406            // We need to add the groups first
407            .try_for_each(|g| dbtxn.update_group(g, offset.as_secs()))
408            .and_then(|_|
409                // So that when we add the account it can make the relationships.
410                dbtxn
411                    .update_account(token, offset.as_secs()))
412            .and_then(|_| dbtxn.commit())
413            .map_err(|_| ())
414    }
415
416    async fn set_cache_grouptoken(&self, token: &GroupToken) -> Result<(), ()> {
417        // Set an expiry
418        let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds);
419        let offset = ex_time
420            .duration_since(SystemTime::UNIX_EPOCH)
421            .map_err(|e| {
422                error!("time conversion error - ex_time less than epoch? {:?}", e);
423            })?;
424
425        let mut dbtxn = self.db.write().await;
426        dbtxn
427            .update_group(token, offset.as_secs())
428            .and_then(|_| dbtxn.commit())
429            .map_err(|_| ())
430    }
431
432    async fn delete_cache_usertoken(&self, a_uuid: Uuid) -> Result<(), ()> {
433        let mut dbtxn = self.db.write().await;
434        dbtxn
435            .delete_account(a_uuid)
436            .and_then(|_| dbtxn.commit())
437            .map_err(|_| ())
438    }
439
440    async fn delete_cache_grouptoken(&self, g_uuid: Uuid) -> Result<(), ()> {
441        let mut dbtxn = self.db.write().await;
442        dbtxn
443            .delete_group(g_uuid)
444            .and_then(|_| dbtxn.commit())
445            .map_err(|_| ())
446    }
447
448    async fn refresh_usertoken(
449        &self,
450        account_id: &Id,
451        token: Option<UserToken>,
452    ) -> Result<Option<UserToken>, ()> {
453        // TODO: Move this to the caller.
454        let now = SystemTime::now();
455
456        let mut hsm_lock = self.hsm.lock().await;
457
458        // We need to re-acquire the token now behind the hsmlock - this is so that
459        // we know that as we write the updated token, we know that no one else has
460        // written to this token, since we are now the only task that is allowed
461        // to be in a write phase.
462        let token = if token.is_some() {
463            self.get_cached_usertoken(account_id)
464                .await
465                .map(|(_expired, option_token)| option_token)
466                .map_err(|err| {
467                    debug!(?err, "get_usertoken error");
468                })?
469        } else {
470            // Was already none, leave it that way.
471            None
472        };
473
474        let user_get_result = if let Some(tok) = token.as_ref() {
475            // Re-use the provider that the token is from.
476            match self.client_ids.get(&tok.provider) {
477                Some(client) => {
478                    client
479                        .unix_user_get(account_id, token.as_ref(), hsm_lock.deref_mut(), now)
480                        .await
481                }
482                None => {
483                    error!(provider = ?tok.provider, "Token was resolved by a provider that no longer appears to be present.");
484
485                    // We don't want to use a token from a former provider, we want it refreshed,
486                    // so lets indicate that we didn't find the token. If we return useCcahed like
487                    // we did previously, we'd never clear and reset this token since we'd never
488                    // locate it's provider.
489                    Ok(UserTokenState::NotFound)
490                }
491            }
492        } else {
493            // We've never seen it before, so iterate over the providers in priority order.
494            'search: {
495                for client in self.clients.iter() {
496                    match client
497                        .unix_user_get(account_id, token.as_ref(), hsm_lock.deref_mut(), now)
498                        .await
499                    {
500                        // Ignore this one.
501                        Ok(UserTokenState::NotFound) => {}
502                        result => break 'search result,
503                    }
504                }
505                break 'search Ok(UserTokenState::NotFound);
506            }
507        };
508
509        match user_get_result {
510            Ok(UserTokenState::Update(mut n_tok)) => {
511                // We have the token!
512                self.set_cache_usertoken(&mut n_tok, hsm_lock.deref_mut())
513                    .await?;
514                Ok(Some(n_tok))
515            }
516            Ok(UserTokenState::NotFound) => {
517                // It previously existed, so now purge it.
518                if let Some(tok) = token {
519                    self.delete_cache_usertoken(tok.uuid).await?;
520                };
521                // Cache the NX here.
522                self.set_nxcache(account_id).await;
523                Ok(None)
524            }
525            Ok(UserTokenState::UseCached) => Ok(token),
526            Err(err) => {
527                // Something went wrong, we don't know what, but lets return the token
528                // anyway.
529                error!(?err);
530                Ok(token)
531            }
532        }
533    }
534
535    async fn refresh_grouptoken(
536        &self,
537        grp_id: &Id,
538        token: Option<GroupToken>,
539    ) -> Result<Option<GroupToken>, ()> {
540        // TODO: Move this to the caller.
541        let now = SystemTime::now();
542
543        let mut hsm_lock = self.hsm.lock().await;
544
545        let group_get_result = if let Some(tok) = token.as_ref() {
546            // Re-use the provider that the token is from.
547            match self.client_ids.get(&tok.provider) {
548                Some(client) => {
549                    client
550                        .unix_group_get(grp_id, hsm_lock.deref_mut(), now)
551                        .await
552                }
553                None => {
554                    error!(provider = ?tok.provider, "Token was resolved by a provider that no longer appears to be present.");
555                    // We don't want to use a token from a former provider, we want it refreshed,
556                    // so lets indicate that we didn't find the token. If we return useCcahed like
557                    // we did previously, we'd never clear and reset this token since we'd never
558                    // locate it's provider.
559                    Ok(GroupTokenState::NotFound)
560                }
561            }
562        } else {
563            // We've never seen it before, so iterate over the providers in priority order.
564            'search: {
565                for client in self.clients.iter() {
566                    match client
567                        .unix_group_get(grp_id, hsm_lock.deref_mut(), now)
568                        .await
569                    {
570                        // Ignore this one.
571                        Ok(GroupTokenState::NotFound) => {}
572                        result => break 'search result,
573                    }
574                }
575                break 'search Ok(GroupTokenState::NotFound);
576            }
577        };
578
579        drop(hsm_lock);
580
581        match group_get_result {
582            Ok(GroupTokenState::Update(n_tok)) => {
583                self.set_cache_grouptoken(&n_tok).await?;
584                Ok(Some(n_tok))
585            }
586            Ok(GroupTokenState::NotFound) => {
587                if let Some(tok) = token {
588                    self.delete_cache_grouptoken(tok.uuid).await?;
589                };
590                // Cache the NX here.
591                self.set_nxcache(grp_id).await;
592                Ok(None)
593            }
594            Ok(GroupTokenState::UseCached) => Ok(token),
595            Err(err) => {
596                // Some other transient error, continue with the token.
597                error!(?err);
598                Ok(token)
599            }
600        }
601    }
602
603    #[instrument(level = "debug", skip(self))]
604    async fn get_usertoken(&self, account_id: &Id) -> Result<Option<UserToken>, ()> {
605        // get the item from the cache
606        let (expired, item) = self.get_cached_usertoken(account_id).await.map_err(|e| {
607            debug!("get_usertoken error -> {:?}", e);
608        })?;
609
610        // If the token isn't found, get_cached will set expired = true.
611        if expired {
612            self.refresh_usertoken(account_id, item).await
613        } else {
614            // Still valid, return the cached entry.
615            Ok(item)
616        }
617        .map(|t| {
618            debug!("token -> {:?}", t);
619            t
620        })
621    }
622
623    #[instrument(level = "debug", skip(self))]
624    async fn get_grouptoken(&self, grp_id: Id) -> Result<Option<GroupToken>, ()> {
625        let (expired, item) = self.get_cached_grouptoken(&grp_id).await.map_err(|e| {
626            debug!("get_grouptoken error -> {:?}", e);
627        })?;
628
629        if expired {
630            self.refresh_grouptoken(&grp_id, item).await
631        } else {
632            // Still valid, return the cached entry.
633            Ok(item)
634        }
635        .map(|t| {
636            debug!("token -> {:?}", t);
637            t
638        })
639    }
640
641    async fn get_groupmembers(&self, g_uuid: Uuid) -> Vec<String> {
642        let mut dbtxn = self.db.write().await;
643
644        dbtxn
645            .get_group_members(g_uuid)
646            .unwrap_or_else(|_| Vec::new())
647            .into_iter()
648            .map(|ut| self.token_uidattr(&ut))
649            .collect()
650    }
651
652    // Get ssh keys for an account id
653    #[instrument(level = "debug", skip(self))]
654    pub async fn get_sshkeys(&self, account_id: &str) -> Result<Vec<String>, ()> {
655        let token = self
656            .get_usertoken(&Id::Name(account_id.to_string()))
657            .await?;
658        Ok(token
659            .map(|t| {
660                // Only return keys if the account is valid
661                if t.valid {
662                    t.sshkeys
663                } else {
664                    Vec::with_capacity(0)
665                }
666            })
667            .unwrap_or_else(|| Vec::with_capacity(0)))
668    }
669
670    fn token_homedirectory_alias(&self, token: &UserToken) -> Option<String> {
671        let is_primary_origin = token.provider == self.primary_origin;
672        self.home_alias.map(|t| match t {
673            // If we have an alias. use it.
674            HomeAttr::Name if is_primary_origin => token.name.as_str().to_string(),
675            HomeAttr::Uuid => token.uuid.hyphenated().to_string(),
676            HomeAttr::Spn | HomeAttr::Name => token.spn.as_str().to_string(),
677        })
678    }
679
680    fn token_homedirectory_attr(&self, token: &UserToken) -> String {
681        let is_primary_origin = token.provider == self.primary_origin;
682        match self.home_attr {
683            HomeAttr::Name if is_primary_origin => token.name.as_str().to_string(),
684            HomeAttr::Uuid => token.uuid.hyphenated().to_string(),
685            HomeAttr::Spn | HomeAttr::Name => token.spn.as_str().to_string(),
686        }
687    }
688
689    fn token_homedirectory(&self, token: &UserToken) -> String {
690        self.token_homedirectory_alias(token)
691            .unwrap_or_else(|| self.token_homedirectory_attr(token))
692    }
693
694    fn token_abs_homedirectory(&self, token: &UserToken) -> String {
695        self.home_prefix
696            .join(self.token_homedirectory(token))
697            .to_string_lossy()
698            .to_string()
699    }
700
701    fn token_uidattr(&self, token: &UserToken) -> String {
702        let is_primary_origin = token.provider == self.primary_origin;
703        match self.uid_attr_map {
704            UidAttr::Name if is_primary_origin => token.name.as_str(),
705            UidAttr::Spn | UidAttr::Name => token.spn.as_str(),
706        }
707        .to_string()
708    }
709
710    #[instrument(level = "debug", skip_all)]
711    pub async fn get_nssaccounts(&self) -> Result<Vec<NssUser>, ()> {
712        // We don't need to filter the cached tokens as the cache shouldn't
713        // have anything that collides with system.
714        let system_nss_users = self.system_provider.get_nssaccounts().await;
715
716        let cached = self.get_cached_usertokens().await?;
717
718        Ok(system_nss_users
719            .into_iter()
720            .chain(cached.into_iter().map(|tok| NssUser {
721                homedir: self.token_abs_homedirectory(&tok),
722                name: self.token_uidattr(&tok),
723                uid: tok.gidnumber,
724                gid: tok.gidnumber,
725                gecos: tok.displayname,
726                shell: tok.shell.unwrap_or_else(|| self.default_shell.clone()),
727            }))
728            .collect())
729    }
730
731    #[instrument(level = "debug", skip_all)]
732    async fn get_nssaccount(&self, account_id: Id) -> Result<Option<NssUser>, ()> {
733        if let Some(nss_user) = self.system_provider.get_nssaccount(&account_id).await {
734            debug!("system provider satisfied request");
735            return Ok(Some(nss_user));
736        }
737
738        let token = self.get_usertoken(&account_id).await?;
739        Ok(token.map(|tok| NssUser {
740            homedir: self.token_abs_homedirectory(&tok),
741            name: self.token_uidattr(&tok),
742            uid: tok.gidnumber,
743            gid: tok.gidnumber,
744            gecos: tok.displayname,
745            shell: tok.shell.unwrap_or_else(|| self.default_shell.clone()),
746        }))
747    }
748
749    #[instrument(level = "debug", skip(self))]
750    pub async fn get_nssaccount_name(&self, account_id: &str) -> Result<Option<NssUser>, ()> {
751        self.get_nssaccount(Id::Name(account_id.to_string())).await
752    }
753
754    #[instrument(level = "debug", skip(self))]
755    pub async fn get_nssaccount_gid(&self, gid: u32) -> Result<Option<NssUser>, ()> {
756        self.get_nssaccount(Id::Gid(gid)).await
757    }
758
759    fn token_gidattr(&self, token: &GroupToken) -> String {
760        match self.gid_attr_map {
761            UidAttr::Spn => token.spn.as_str(),
762            UidAttr::Name => token.name.as_str(),
763        }
764        .to_string()
765    }
766
767    #[instrument(level = "debug", skip_all)]
768    pub async fn get_nssgroups(&self) -> Result<Vec<NssGroup>, ()> {
769        let mut r = self.system_provider.get_nssgroups().await;
770
771        // Extend all the local groups if maps exist.
772        for nss_group in r.iter_mut() {
773            for client in self.clients.iter() {
774                if let Some(extend_group_id) = client.has_map_group(&nss_group.name) {
775                    let (_, token) = self.get_cached_grouptoken(extend_group_id).await?;
776                    if let Some(token) = token {
777                        let members = self.get_groupmembers(token.uuid).await;
778                        nss_group.members.extend(members);
779                        debug!(
780                            "extended group {} with members from {}",
781                            nss_group.name, token.name
782                        );
783                    }
784                }
785            }
786        }
787
788        let l = self.get_cached_grouptokens().await?;
789        r.reserve(l.len());
790        for tok in l.into_iter() {
791            let members = self.get_groupmembers(tok.uuid).await;
792            r.push(NssGroup {
793                name: self.token_gidattr(&tok),
794                gid: tok.gidnumber,
795                members,
796            })
797        }
798        Ok(r)
799    }
800
801    async fn get_nssgroup(&self, grp_id: Id) -> Result<Option<NssGroup>, ()> {
802        if let Some(mut nss_group) = self.system_provider.get_nssgroup(&grp_id).await {
803            debug!("system provider satisfied request");
804
805            for client in self.clients.iter() {
806                if let Some(extend_group_id) = client.has_map_group(&nss_group.name) {
807                    let token = self.get_grouptoken(extend_group_id.clone()).await?;
808                    if let Some(token) = token {
809                        let members = self.get_groupmembers(token.uuid).await;
810                        nss_group.members.extend(members);
811                        debug!(
812                            "extended group {} with members from {}",
813                            nss_group.name, token.name
814                        );
815                    }
816                }
817            }
818
819            nss_group.members.sort_unstable();
820            nss_group.members.dedup();
821
822            return Ok(Some(nss_group));
823        }
824
825        let token = self.get_grouptoken(grp_id).await?;
826        // Get members set.
827        match token {
828            Some(tok) => {
829                let members = self.get_groupmembers(tok.uuid).await;
830                Ok(Some(NssGroup {
831                    name: self.token_gidattr(&tok),
832                    gid: tok.gidnumber,
833                    members,
834                }))
835            }
836            None => Ok(None),
837        }
838    }
839
840    #[instrument(level = "debug", skip(self))]
841    pub async fn get_nssgroup_name(&self, grp_id: &str) -> Result<Option<NssGroup>, ()> {
842        self.get_nssgroup(Id::Name(grp_id.to_string())).await
843    }
844
845    #[instrument(level = "debug", skip(self))]
846    pub async fn get_nssgroup_gid(&self, gid: u32) -> Result<Option<NssGroup>, ()> {
847        self.get_nssgroup(Id::Gid(gid)).await
848    }
849
850    #[instrument(level = "debug", skip(self))]
851    pub async fn pam_account_allowed(&self, account_id: &str) -> Result<Option<bool>, ()> {
852        let id = Id::Name(account_id.to_string());
853
854        if let Some(answer) = self.system_provider.authorise(&id).await {
855            return Ok(Some(answer));
856        };
857
858        // Not a system account, handle with the provider.
859        let token = self.get_usertoken(&id).await?;
860
861        // If there is no token, return Ok(None) to trigger unknown-user path in pam.
862        match token {
863            Some(token) => {
864                let client = self.client_ids.get(&token.provider)
865                    .cloned()
866                    .ok_or_else(|| {
867                        error!(provider = ?token.provider, "Token was resolved by a provider that no longer appears to be present.");
868                    })?;
869
870                client.unix_user_authorise(&token).await.map_err(|err| {
871                    error!(?err, "unable to authorise account");
872                })
873            }
874            None => Ok(None),
875        }
876    }
877
878    #[instrument(level = "debug", skip(self, shutdown_rx))]
879    pub async fn pam_account_authenticate_init(
880        &self,
881        account_id: &str,
882        pam_info: &PamServiceInfo,
883        current_time: OffsetDateTime,
884        shutdown_rx: broadcast::Receiver<()>,
885    ) -> Result<(AuthSession, PamAuthResponse), ()> {
886        // Setup an auth session. If possible bring the resolver online.
887        // Further steps won't attempt to bring the cache online to prevent
888        // weird interactions - they should assume online/offline only for
889        // the duration of their operation. A failure of connectivity during
890        // an online operation will take the cache offline however.
891        let now = SystemTime::now();
892
893        let id = Id::Name(account_id.to_string());
894
895        match self.system_provider.auth_init(&id, current_time).await {
896            // The system provider will not take part in this authentication.
897            SystemProviderAuthInit::Ignore => {
898                debug!(?account_id, "account unknown to system provider, continue.");
899            }
900            // The provider knows the account, and is unable to proceed,
901            // We return unknown here so that pam_kanidm can be skipped and fall back
902            // to pam_unix.so.
903            SystemProviderAuthInit::ShadowMissing => {
904                warn!(
905                    ?account_id,
906                    "Resolver unable to proceed, /etc/shadow was not accessible."
907                );
908                return Ok((AuthSession::Denied, PamAuthResponse::Unknown));
909            }
910            // There are no credentials for this account
911            SystemProviderAuthInit::CredentialsUnavailable => {
912                warn!(
913                    ?account_id,
914                    "Denying auth request for system user with no valid credentials"
915                );
916                return Ok((AuthSession::Denied, PamAuthResponse::Denied));
917            }
918            // The account has expired
919            SystemProviderAuthInit::Expired => {
920                warn!(
921                    ?account_id,
922                    "Denying auth request for system user with expired credentials"
923                );
924                return Ok((AuthSession::Denied, PamAuthResponse::Denied));
925            }
926            // The provider knows the account and wants to proceed,
927            SystemProviderAuthInit::Begin {
928                next_request,
929                cred_handler,
930                shadow,
931            } => {
932                let auth_session = AuthSession::System {
933                    account_id: account_id.to_string(),
934                    id,
935                    shadow,
936                    cred_handler,
937                };
938
939                return Ok((auth_session, next_request.into()));
940            }
941        }
942
943        let token = self.get_usertoken(&id).await?;
944
945        // Get the provider associated to this token.
946
947        let mut hsm_lock = self.hsm.lock().await;
948
949        // We don't care if we are expired - we will always attempt to go
950        // online and perform this operation online if possible.
951
952        if let Some(token) = token {
953            // We have a token, we know what provider is needed
954            let client = self.client_ids.get(&token.provider)
955                .cloned()
956                .ok_or_else(|| {
957                    error!(provider = ?token.provider, "Token was resolved by a provider that no longer appears to be present.");
958                })?;
959
960            let online_at_init = client.attempt_online(hsm_lock.deref_mut(), now).await;
961            // if we are online, we try and start an online auth.
962            debug!(?online_at_init);
963
964            if online_at_init {
965                let init_result = client
966                    .unix_user_online_auth_init(
967                        account_id,
968                        &token,
969                        hsm_lock.deref_mut(),
970                        &shutdown_rx,
971                    )
972                    .await;
973
974                match init_result {
975                    Ok((next_req, cred_handler)) => {
976                        let auth_session = AuthSession::Online {
977                            client,
978                            account_id: account_id.to_string(),
979                            id,
980                            cred_handler,
981                            shutdown_rx,
982                        };
983                        Ok((auth_session, next_req.into()))
984                    }
985                    Err(err) => {
986                        error!(?err, "Unable to start authentication");
987                        Err(())
988                    }
989                }
990            } else {
991                // Can the auth proceed offline?
992                let init_result = client.unix_user_offline_auth_init(&token).await;
993
994                match init_result {
995                    Ok((next_req, cred_handler)) => {
996                        let auth_session = AuthSession::Offline {
997                            account_id: account_id.to_string(),
998                            id,
999                            client,
1000                            session_token: Box::new(token),
1001                            cred_handler,
1002                        };
1003                        Ok((auth_session, next_req.into()))
1004                    }
1005                    Err(err) => {
1006                        error!(?err, "Unable to start authentication");
1007                        Err(())
1008                    }
1009                }
1010            }
1011        } else {
1012            // We don't know anything about this user. Can we try to auth them?
1013
1014            // TODO: If any provider is offline should we fail the auth? I can imagine a possible
1015            // issue where if we had provides A, B, C stacked, and A was offline, then B could
1016            // service an auth that A *should* have serviced.
1017
1018            for client in self.clients.iter() {
1019                let online_at_init = client.attempt_online(hsm_lock.deref_mut(), now).await;
1020                debug!(?online_at_init);
1021
1022                if !online_at_init {
1023                    warn!(?account_id, "Unable to proceed with authentication, all providers must be online for unknown user authentication.");
1024                    return Ok((AuthSession::Denied, PamAuthResponse::Unknown));
1025                }
1026            }
1027
1028            for client in self.clients.iter() {
1029                let init_result = client
1030                    .unix_unknown_user_online_auth_init(
1031                        account_id,
1032                        hsm_lock.deref_mut(),
1033                        &shutdown_rx,
1034                    )
1035                    .await;
1036
1037                match init_result {
1038                    Ok(Some((next_req, cred_handler))) => {
1039                        let auth_session = AuthSession::Online {
1040                            client: client.clone(),
1041                            account_id: account_id.to_string(),
1042                            id,
1043                            cred_handler,
1044                            shutdown_rx,
1045                        };
1046                        return Ok((auth_session, next_req.into()));
1047                    }
1048                    Ok(None) => {
1049                        // Not for us, check the next provider.
1050                    }
1051                    Err(err) => {
1052                        error!(?err, "Unable to start authentication");
1053                        return Err(());
1054                    }
1055                }
1056            }
1057
1058            // No module signaled that they want it, bail.
1059            warn!("No provider is willing to service authentication of unknown account.");
1060            Ok((AuthSession::Denied, PamAuthResponse::Unknown))
1061        }
1062    }
1063
1064    #[instrument(level = "debug", skip_all)]
1065    pub async fn pam_account_authenticate_step(
1066        &self,
1067        auth_session: &mut AuthSession,
1068        pam_next_req: PamAuthRequest,
1069    ) -> Result<PamAuthResponse, ()> {
1070        let mut hsm_lock = self.hsm.lock().await;
1071
1072        let maybe_err = match &mut *auth_session {
1073            &mut AuthSession::Online {
1074                ref client,
1075                ref account_id,
1076                ref id,
1077                ref mut cred_handler,
1078                ref shutdown_rx,
1079            } => {
1080                // This is not used in the authentication, but is so that any new
1081                // extra keys or data on the token are updated correctly if the authentication
1082                // requests an update. Since we hold the hsm_lock, no other task can
1083                // update this token between now and completion of the fn.
1084                let current_token = self
1085                    .get_cached_usertoken(id)
1086                    .await
1087                    .map(|(_expired, option_token)| option_token)
1088                    .map_err(|err| {
1089                        debug!(?err, "get_usertoken error");
1090                    })?;
1091
1092                let result = client
1093                    .unix_user_online_auth_step(
1094                        account_id,
1095                        current_token.as_ref(),
1096                        cred_handler,
1097                        pam_next_req,
1098                        hsm_lock.deref_mut(),
1099                        shutdown_rx,
1100                    )
1101                    .await;
1102
1103                match result {
1104                    Ok(AuthResult::SuccessUpdate { .. } | AuthResult::Success) => {
1105                        info!(?account_id, "Authentication Success");
1106                    }
1107                    Ok(AuthResult::Denied) => {
1108                        info!(?account_id, "Authentication Denied");
1109                    }
1110                    Ok(AuthResult::Next(_)) => {
1111                        info!(?account_id, "Authentication Continue");
1112                    }
1113                    _ => {}
1114                };
1115
1116                result
1117            }
1118            &mut AuthSession::Offline {
1119                ref account_id,
1120                ref id,
1121                ref client,
1122                ref session_token,
1123                ref mut cred_handler,
1124            } => {
1125                // This is not used in the authentication, but is so that any new
1126                // extra keys or data on the token are updated correctly if the authentication
1127                // requests an update. Since we hold the hsm_lock, no other task can
1128                // update this token between now and completion of the fn.
1129                let current_token = self
1130                    .get_cached_usertoken(id)
1131                    .await
1132                    .map(|(_expired, option_token)| option_token)
1133                    .map_err(|err| {
1134                        debug!(?err, "get_usertoken error");
1135                    })?;
1136
1137                // We are offline, continue. Remember, authsession should have
1138                // *everything you need* to proceed here!
1139                let result = client
1140                    .unix_user_offline_auth_step(
1141                        current_token.as_ref(),
1142                        session_token,
1143                        cred_handler,
1144                        pam_next_req,
1145                        hsm_lock.deref_mut(),
1146                    )
1147                    .await;
1148
1149                match result {
1150                    Ok(AuthResult::SuccessUpdate { .. } | AuthResult::Success) => {
1151                        info!(?account_id, "Authentication Success");
1152                    }
1153                    Ok(AuthResult::Denied) => {
1154                        info!(?account_id, "Authentication Denied");
1155                    }
1156                    Ok(AuthResult::Next(_)) => {
1157                        info!(?account_id, "Authentication Continue");
1158                    }
1159                    _ => {}
1160                };
1161
1162                result
1163            }
1164            &mut AuthSession::System {
1165                ref account_id,
1166                id: _,
1167                ref mut cred_handler,
1168                ref shadow,
1169            } => {
1170                // I had a lot of thoughts here, but I think system auth is
1171                // not the same as provider, so I think we special case here and have a separate
1172                // return type.
1173                let system_auth_result = shadow.auth_step(cred_handler, pam_next_req);
1174
1175                let next = match system_auth_result {
1176                    SystemAuthResult::Denied => {
1177                        info!(?account_id, "Authentication Denied");
1178
1179                        *auth_session = AuthSession::Denied;
1180
1181                        Ok(PamAuthResponse::Denied)
1182                    }
1183                    SystemAuthResult::Success => {
1184                        info!(?account_id, "Authentication Success");
1185
1186                        *auth_session = AuthSession::Success;
1187
1188                        Ok(PamAuthResponse::Success)
1189                    }
1190                    SystemAuthResult::Next(req) => Ok(req.into()),
1191                };
1192
1193                // We shortcut here
1194                return next;
1195            }
1196            &mut AuthSession::Success | &mut AuthSession::Denied => Err(IdpError::BadRequest),
1197        };
1198
1199        match maybe_err {
1200            // What did the provider direct us to do next?
1201            Ok(AuthResult::Success) => {
1202                *auth_session = AuthSession::Success;
1203                Ok(PamAuthResponse::Success)
1204            }
1205            Ok(AuthResult::SuccessUpdate { mut new_token }) => {
1206                self.set_cache_usertoken(&mut new_token, hsm_lock.deref_mut())
1207                    .await?;
1208                *auth_session = AuthSession::Success;
1209
1210                Ok(PamAuthResponse::Success)
1211            }
1212            Ok(AuthResult::Denied) => {
1213                *auth_session = AuthSession::Denied;
1214
1215                Ok(PamAuthResponse::Denied)
1216            }
1217            Ok(AuthResult::Next(req)) => Ok(req.into()),
1218            Err(IdpError::NotFound) => {
1219                *auth_session = AuthSession::Denied;
1220
1221                Ok(PamAuthResponse::Unknown)
1222            }
1223            Err(err) => {
1224                *auth_session = AuthSession::Denied;
1225
1226                error!(?err, "Unable to proceed, failing the session");
1227                Err(())
1228            }
1229        }
1230    }
1231
1232    // Can this be cfg debug/test?
1233    #[instrument(level = "debug", skip(self, password))]
1234    pub async fn pam_account_authenticate(
1235        &self,
1236        account_id: &str,
1237        current_time: OffsetDateTime,
1238        password: &str,
1239    ) -> Result<Option<bool>, ()> {
1240        let (_shutdown_tx, shutdown_rx) = broadcast::channel(1);
1241
1242        let pam_info = PamServiceInfo {
1243            service: "kanidm-unix-test".to_string(),
1244            tty: Some("/dev/null".to_string()),
1245            rhost: None,
1246        };
1247
1248        let mut auth_session = match self
1249            .pam_account_authenticate_init(account_id, &pam_info, current_time, shutdown_rx)
1250            .await?
1251        {
1252            (auth_session, PamAuthResponse::Password) => {
1253                // Can continue!
1254                auth_session
1255            }
1256            (auth_session, PamAuthResponse::DeviceAuthorizationGrant { .. }) => {
1257                // Can continue!
1258                auth_session
1259            }
1260            (auth_session, PamAuthResponse::MFACode { .. }) => {
1261                // Can continue!
1262                auth_session
1263            }
1264            (auth_session, PamAuthResponse::MFAPoll { .. }) => {
1265                // Can continue!
1266                auth_session
1267            }
1268            (auth_session, PamAuthResponse::MFAPollWait) => {
1269                // Can continue!
1270                auth_session
1271            }
1272            (auth_session, PamAuthResponse::SetupPin { .. }) => {
1273                // Can continue!
1274                auth_session
1275            }
1276            (auth_session, PamAuthResponse::Pin) => {
1277                // Can continue!
1278                auth_session
1279            }
1280            (_, PamAuthResponse::Unknown) => return Ok(None),
1281            (_, PamAuthResponse::Denied) => return Ok(Some(false)),
1282            (_, PamAuthResponse::Success) => {
1283                // Should never get here "off the rip".
1284                debug_assert!(false);
1285                return Ok(Some(true));
1286            }
1287        };
1288
1289        // Now we can make the next step.
1290        let pam_next_req = PamAuthRequest::Password {
1291            cred: password.to_string(),
1292        };
1293        match self
1294            .pam_account_authenticate_step(&mut auth_session, pam_next_req)
1295            .await?
1296        {
1297            PamAuthResponse::Success => Ok(Some(true)),
1298            PamAuthResponse::Denied => Ok(Some(false)),
1299            _ => {
1300                // Should not be able to get here, if the user was unknown they should
1301                // be out. If it wants more mechanisms, we can't proceed here.
1302                // debug_assert!(false);
1303                Ok(None)
1304            }
1305        }
1306    }
1307
1308    #[instrument(level = "debug", skip(self))]
1309    pub async fn pam_account_beginsession(
1310        &self,
1311        account_id: &str,
1312    ) -> Result<Option<HomeDirectoryInfo>, ()> {
1313        let id = Id::Name(account_id.to_string());
1314
1315        match self.system_provider.begin_session(&id).await {
1316            SystemProviderSession::Start => {
1317                return Ok(None);
1318            }
1319            /*
1320            SystemProviderSession::StartCreateHome(
1321                info
1322            ) => {
1323                return Ok(Some(info));
1324            }
1325            */
1326            SystemProviderSession::Ignore => {}
1327        };
1328
1329        // Not a system account, check based on the token and resolve.
1330        let token = self.get_usertoken(&id).await?;
1331        Ok(token.as_ref().map(|tok| HomeDirectoryInfo {
1332            uid: tok.gidnumber,
1333            gid: tok.gidnumber,
1334            name: self.token_homedirectory_attr(tok),
1335            aliases: self
1336                .token_homedirectory_alias(tok)
1337                .map(|s| vec![s])
1338                .unwrap_or_default(),
1339        }))
1340    }
1341
1342    pub async fn provider_status(&self) -> Vec<ProviderStatus> {
1343        let now = SystemTime::now();
1344        let mut hsm_lock = self.hsm.lock().await;
1345
1346        let mut results = Vec::with_capacity(self.clients.len() + 1);
1347
1348        results.push(ProviderStatus {
1349            name: "system".to_string(),
1350            online: true,
1351        });
1352
1353        for client in self.clients.iter() {
1354            let online = client.attempt_online(hsm_lock.deref_mut(), now).await;
1355
1356            let name = client.origin().to_string();
1357
1358            results.push(ProviderStatus { name, online })
1359        }
1360
1361        results
1362    }
1363
1364    #[instrument(level = "debug", skip_all)]
1365    pub async fn test_connection(&self) -> bool {
1366        let now = SystemTime::now();
1367        let mut hsm_lock = self.hsm.lock().await;
1368
1369        for client in self.clients.iter() {
1370            let status = client.attempt_online(hsm_lock.deref_mut(), now).await;
1371
1372            if !status {
1373                return false;
1374            }
1375        }
1376
1377        // All online
1378        true
1379    }
1380}