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