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