kanidm_unix_common/
unix_config.rs

1//! This is configuration definitions and parser for the various unix integration
2//! tools and services. This needs to support a number of use cases like pam/nss
3//! modules parsing the config quickly and the unix daemon which has to connect to
4//! various backend sources.
5//!
6//! To achieve this the configuration has two main sections - the configuration
7//! specification which will be parsed by the tools, then the configuration as
8//! relevant to that tool.
9
10use crate::constants::*;
11#[cfg(all(target_family = "unix", feature = "selinux"))]
12use crate::selinux_util;
13use crate::unix_passwd::UnixIntegrationError;
14use serde::Deserialize;
15use std::env;
16use std::fmt::{Display, Formatter};
17use std::fs::{read_to_string, File};
18use std::io::{ErrorKind, Read};
19use std::path::{Path, PathBuf};
20
21#[derive(Debug, Copy, Clone)]
22pub enum HomeAttr {
23    Uuid,
24    Spn,
25    Name,
26}
27
28impl Display for HomeAttr {
29    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30        write!(
31            f,
32            "{}",
33            match self {
34                HomeAttr::Uuid => "UUID",
35                HomeAttr::Spn => "SPN",
36                HomeAttr::Name => "Name",
37            }
38        )
39    }
40}
41
42#[derive(Debug, Copy, Clone)]
43pub enum UidAttr {
44    Name,
45    Spn,
46}
47
48impl Display for UidAttr {
49    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
50        write!(
51            f,
52            "{}",
53            match self {
54                UidAttr::Name => "Name",
55                UidAttr::Spn => "SPN",
56            }
57        )
58    }
59}
60
61#[derive(Debug, Clone, Default)]
62pub enum HsmType {
63    #[cfg_attr(not(feature = "tpm"), default)]
64    Soft,
65    #[cfg_attr(feature = "tpm", default)]
66    TpmIfPossible,
67    Tpm,
68}
69
70impl Display for HsmType {
71    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
72        match self {
73            HsmType::Soft => write!(f, "Soft"),
74            HsmType::TpmIfPossible => write!(f, "Tpm if possible"),
75            HsmType::Tpm => write!(f, "Tpm"),
76        }
77    }
78}
79
80// Allowed as the large enum is only short lived at startup to the true config
81#[allow(clippy::large_enum_variant)]
82// This bit of magic lets us deserialise the old config and the new versions.
83#[derive(Debug, Deserialize)]
84#[serde(untagged)]
85enum ConfigUntagged {
86    Versioned(ConfigVersion),
87    Legacy(ConfigInt),
88}
89
90#[derive(Debug, Deserialize)]
91#[serde(tag = "version")]
92enum ConfigVersion {
93    #[serde(rename = "2")]
94    V2 {
95        #[serde(flatten)]
96        values: ConfigV2,
97    },
98}
99
100#[derive(Debug, Deserialize, Default)]
101#[serde(rename_all = "lowercase")]
102enum HomeStrategyV2 {
103    #[default]
104    Symlink,
105}
106
107#[derive(Debug, Deserialize)]
108#[serde(deny_unknown_fields)]
109/// This is the version 2 of the JSON configuration specification for the unixd suite.
110struct ConfigV2 {
111    cache_db_path: Option<String>,
112    sock_path: Option<String>,
113    task_sock_path: Option<String>,
114
115    cache_timeout: Option<u64>,
116
117    default_shell: Option<String>,
118    home_prefix: Option<String>,
119    home_mount_prefix: Option<String>,
120    home_attr: Option<String>,
121    home_alias: Option<String>,
122    #[serde(default)]
123    home_strategy: HomeStrategyV2,
124    use_etc_skel: Option<bool>,
125    uid_attr_map: Option<String>,
126    gid_attr_map: Option<String>,
127    selinux: Option<bool>,
128
129    hsm_pin_path: Option<String>,
130    hsm_type: Option<String>,
131    tpm_tcti_name: Option<String>,
132
133    kanidm: Option<KanidmConfigV2>,
134}
135
136#[derive(Clone, Debug, Deserialize)]
137pub struct GroupMap {
138    pub local: String,
139    pub with: String,
140}
141
142#[derive(Debug, Deserialize)]
143struct KanidmConfigV2 {
144    conn_timeout: Option<u64>,
145    request_timeout: Option<u64>,
146    pam_allowed_login_groups: Option<Vec<String>>,
147    #[serde(default)]
148    map_group: Vec<GroupMap>,
149    service_account_token_path: Option<PathBuf>,
150}
151
152#[derive(Debug, Deserialize)]
153/// This is the version 1 of the JSON configuration specification for the unixd suite.
154struct ConfigInt {
155    db_path: Option<String>,
156    sock_path: Option<String>,
157    task_sock_path: Option<String>,
158    conn_timeout: Option<u64>,
159    request_timeout: Option<u64>,
160    cache_timeout: Option<u64>,
161    pam_allowed_login_groups: Option<Vec<String>>,
162    default_shell: Option<String>,
163    home_prefix: Option<String>,
164    home_mount_prefix: Option<String>,
165    home_attr: Option<String>,
166    home_alias: Option<String>,
167    use_etc_skel: Option<bool>,
168    uid_attr_map: Option<String>,
169    gid_attr_map: Option<String>,
170    selinux: Option<bool>,
171    #[serde(default)]
172    allow_local_account_override: Vec<String>,
173
174    hsm_pin_path: Option<String>,
175    hsm_type: Option<String>,
176    tpm_tcti_name: Option<String>,
177
178    // Detect and warn on values in these places - this is to catch
179    // when someone is using a v2 value on a v1 config.
180    #[serde(default)]
181    cache_db_path: Option<toml::value::Value>,
182    #[serde(default)]
183    kanidm: Option<toml::value::Value>,
184}
185
186// ========================================================================
187
188#[derive(Debug)]
189/// This is the parsed Kanidm provider configuration that the Unixd resolver
190/// will use to connect to Kanidm.
191pub struct KanidmConfig {
192    pub conn_timeout: u64,
193    pub request_timeout: u64,
194    pub pam_allowed_login_groups: Vec<String>,
195    pub map_group: Vec<GroupMap>,
196    pub service_account_token: Option<String>,
197}
198
199#[derive(Debug, Default)]
200pub enum HomeStrategy {
201    #[default]
202    Symlink,
203}
204
205#[derive(Debug)]
206/// This is the parsed configuration for the Unixd resolver.
207pub struct UnixdConfig {
208    pub cache_db_path: String,
209    pub sock_path: String,
210    pub task_sock_path: String,
211    pub cache_timeout: u64,
212    pub unix_sock_timeout: u64,
213    pub default_shell: String,
214    pub home_prefix: PathBuf,
215    pub home_mount_prefix: Option<PathBuf>,
216    pub home_attr: HomeAttr,
217    pub home_alias: Option<HomeAttr>,
218    pub home_strategy: HomeStrategy,
219    pub use_etc_skel: bool,
220    pub uid_attr_map: UidAttr,
221    pub gid_attr_map: UidAttr,
222    pub selinux: bool,
223    pub hsm_type: HsmType,
224    pub hsm_pin_path: String,
225    pub tpm_tcti_name: String,
226    pub kanidm_config: Option<KanidmConfig>,
227}
228
229impl Default for UnixdConfig {
230    fn default() -> Self {
231        UnixdConfig::new()
232    }
233}
234
235impl Display for UnixdConfig {
236    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
237        writeln!(f, "cache_db_path: {}", &self.cache_db_path)?;
238        writeln!(f, "sock_path: {}", self.sock_path)?;
239        writeln!(f, "task_sock_path: {}", self.task_sock_path)?;
240        writeln!(f, "unix_sock_timeout: {}", self.unix_sock_timeout)?;
241        writeln!(f, "cache_timeout: {}", self.cache_timeout)?;
242        writeln!(f, "default_shell: {}", self.default_shell)?;
243        writeln!(f, "home_prefix: {:?}", self.home_prefix)?;
244        match self.home_mount_prefix.as_deref() {
245            Some(val) => writeln!(f, "home_mount_prefix: {val:?}")?,
246            None => writeln!(f, "home_mount_prefix: unset")?,
247        }
248        writeln!(f, "home_attr: {}", self.home_attr)?;
249        match self.home_alias {
250            Some(val) => writeln!(f, "home_alias: {val}")?,
251            None => writeln!(f, "home_alias: unset")?,
252        }
253
254        writeln!(f, "uid_attr_map: {}", self.uid_attr_map)?;
255        writeln!(f, "gid_attr_map: {}", self.gid_attr_map)?;
256
257        writeln!(f, "hsm_type: {}", self.hsm_type)?;
258        writeln!(f, "tpm_tcti_name: {}", self.tpm_tcti_name)?;
259
260        writeln!(f, "selinux: {}", self.selinux)?;
261
262        if let Some(kconfig) = &self.kanidm_config {
263            writeln!(f, "kanidm: enabled")?;
264            writeln!(
265                f,
266                "kanidm pam_allowed_login_groups: {:#?}",
267                kconfig.pam_allowed_login_groups
268            )?;
269            writeln!(f, "kanidm conn_timeout: {}", kconfig.conn_timeout)?;
270            writeln!(f, "kanidm request_timeout: {}", kconfig.request_timeout)?;
271        } else {
272            writeln!(f, "kanidm: disabled")?;
273        };
274
275        Ok(())
276    }
277}
278
279impl UnixdConfig {
280    pub fn new() -> Self {
281        let cache_db_path = match env::var("KANIDM_CACHE_DB_PATH") {
282            Ok(val) => val,
283            Err(_) => DEFAULT_CACHE_DB_PATH.into(),
284        };
285        let hsm_pin_path = match env::var("KANIDM_HSM_PIN_PATH") {
286            Ok(val) => val,
287            Err(_) => DEFAULT_HSM_PIN_PATH.into(),
288        };
289
290        UnixdConfig {
291            cache_db_path,
292            sock_path: DEFAULT_SOCK_PATH.to_string(),
293            task_sock_path: DEFAULT_TASK_SOCK_PATH.to_string(),
294            unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
295            cache_timeout: DEFAULT_CACHE_TIMEOUT,
296            default_shell: DEFAULT_SHELL.to_string(),
297            home_prefix: DEFAULT_HOME_PREFIX.into(),
298            home_mount_prefix: None,
299            home_attr: DEFAULT_HOME_ATTR,
300            home_alias: DEFAULT_HOME_ALIAS,
301            home_strategy: HomeStrategy::default(),
302            use_etc_skel: DEFAULT_USE_ETC_SKEL,
303            uid_attr_map: DEFAULT_UID_ATTR_MAP,
304            gid_attr_map: DEFAULT_GID_ATTR_MAP,
305            selinux: DEFAULT_SELINUX,
306            hsm_pin_path,
307            hsm_type: HsmType::default(),
308            tpm_tcti_name: DEFAULT_TPM_TCTI_NAME.to_string(),
309
310            kanidm_config: None,
311        }
312    }
313
314    pub fn read_options_from_optional_config<P: AsRef<Path> + std::fmt::Debug>(
315        self,
316        config_path: P,
317    ) -> Result<Self, UnixIntegrationError> {
318        debug!("Attempting to load configuration from {:#?}", &config_path);
319        let mut f = match File::open(&config_path) {
320            Ok(f) => {
321                debug!("Successfully opened configuration file {:#?}", &config_path);
322                f
323            }
324            Err(e) => {
325                match e.kind() {
326                    ErrorKind::NotFound => {
327                        debug!(
328                            "Configuration file {:#?} not found, skipping.",
329                            &config_path
330                        );
331                    }
332                    ErrorKind::PermissionDenied => {
333                        warn!(
334                            "Permission denied loading configuration file {:#?}, skipping.",
335                            &config_path
336                        );
337                    }
338                    _ => {
339                        debug!(
340                            "Unable to open config file {:#?} [{:?}], skipping ...",
341                            &config_path, e
342                        );
343                    }
344                };
345                return Ok(self);
346            }
347        };
348
349        let mut contents = String::new();
350        f.read_to_string(&mut contents).map_err(|e| {
351            error!("{:?}", e);
352            UnixIntegrationError
353        })?;
354
355        let config: ConfigUntagged = toml::from_str(contents.as_str()).map_err(|e| {
356            error!("{:?}", e);
357            UnixIntegrationError
358        })?;
359
360        match config {
361            ConfigUntagged::Legacy(config) => self.apply_from_config_legacy(config),
362            ConfigUntagged::Versioned(ConfigVersion::V2 { values }) => {
363                self.apply_from_config_v2(values)
364            }
365        }
366    }
367
368    fn apply_from_config_legacy(self, config: ConfigInt) -> Result<Self, UnixIntegrationError> {
369        if config.kanidm.is_some() || config.cache_db_path.is_some() {
370            error!("You are using version=\"2\" options in a legacy config. THESE WILL NOT WORK.");
371            return Err(UnixIntegrationError);
372        }
373
374        let map_group = config
375            .allow_local_account_override
376            .iter()
377            .map(|name| GroupMap {
378                local: name.clone(),
379                with: name.clone(),
380            })
381            .collect();
382
383        let kanidm_config = Some(KanidmConfig {
384            conn_timeout: config.conn_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT),
385            request_timeout: config.request_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT * 2),
386            pam_allowed_login_groups: config.pam_allowed_login_groups.unwrap_or_default(),
387            map_group,
388            service_account_token: None,
389        });
390
391        // Now map the values into our config.
392        Ok(UnixdConfig {
393            cache_db_path: config.db_path.unwrap_or(self.cache_db_path),
394            sock_path: config.sock_path.unwrap_or(self.sock_path),
395            task_sock_path: config.task_sock_path.unwrap_or(self.task_sock_path),
396            unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
397            cache_timeout: config.cache_timeout.unwrap_or(self.cache_timeout),
398            default_shell: config.default_shell.unwrap_or(self.default_shell),
399            home_prefix: config
400                .home_prefix
401                .map(|p| p.into())
402                .unwrap_or(self.home_prefix.clone()),
403            home_mount_prefix: config.home_mount_prefix.map(|p| p.into()),
404            home_attr: config
405                .home_attr
406                .and_then(|v| match v.as_str() {
407                    "uuid" => Some(HomeAttr::Uuid),
408                    "spn" => Some(HomeAttr::Spn),
409                    "name" => Some(HomeAttr::Name),
410                    _ => {
411                        warn!("Invalid home_attr configured, using default ...");
412                        None
413                    }
414                })
415                .unwrap_or(self.home_attr),
416            home_alias: config
417                .home_alias
418                .and_then(|v| match v.as_str() {
419                    "none" => Some(None),
420                    "uuid" => Some(Some(HomeAttr::Uuid)),
421                    "spn" => Some(Some(HomeAttr::Spn)),
422                    "name" => Some(Some(HomeAttr::Name)),
423                    _ => {
424                        warn!("Invalid home_alias configured, using default ...");
425                        None
426                    }
427                })
428                .unwrap_or(self.home_alias),
429            home_strategy: HomeStrategy::default(),
430            use_etc_skel: config.use_etc_skel.unwrap_or(self.use_etc_skel),
431            uid_attr_map: config
432                .uid_attr_map
433                .and_then(|v| match v.as_str() {
434                    "spn" => Some(UidAttr::Spn),
435                    "name" => Some(UidAttr::Name),
436                    _ => {
437                        warn!("Invalid uid_attr_map configured, using default ...");
438                        None
439                    }
440                })
441                .unwrap_or(self.uid_attr_map),
442            gid_attr_map: config
443                .gid_attr_map
444                .and_then(|v| match v.as_str() {
445                    "spn" => Some(UidAttr::Spn),
446                    "name" => Some(UidAttr::Name),
447                    _ => {
448                        warn!("Invalid gid_attr_map configured, using default ...");
449                        None
450                    }
451                })
452                .unwrap_or(self.gid_attr_map),
453            selinux: match config.selinux.unwrap_or(self.selinux) {
454                #[cfg(all(target_family = "unix", feature = "selinux"))]
455                true => selinux_util::supported(),
456                _ => false,
457            },
458            hsm_pin_path: config.hsm_pin_path.unwrap_or(self.hsm_pin_path),
459            hsm_type: config
460                .hsm_type
461                .and_then(|v| match v.as_str() {
462                    "soft" => Some(HsmType::Soft),
463                    "tpm_if_possible" => Some(HsmType::TpmIfPossible),
464                    "tpm" => Some(HsmType::Tpm),
465                    _ => {
466                        warn!("Invalid hsm_type configured, using default ...");
467                        None
468                    }
469                })
470                .unwrap_or(self.hsm_type),
471            tpm_tcti_name: config
472                .tpm_tcti_name
473                .unwrap_or(DEFAULT_TPM_TCTI_NAME.to_string()),
474            kanidm_config,
475        })
476    }
477
478    fn apply_from_config_v2(self, config: ConfigV2) -> Result<Self, UnixIntegrationError> {
479        let kanidm_config = if let Some(kconfig) = config.kanidm {
480            let service_account_token_path_env = match env::var("KANIDM_SERVICE_ACCOUNT_TOKEN_PATH")
481            {
482                Ok(val) => val.into(),
483                Err(_) => DEFAULT_KANIDM_SERVICE_ACCOUNT_TOKEN_PATH.into(),
484            };
485
486            let service_account_token_path: PathBuf = kconfig
487                .service_account_token_path
488                .unwrap_or(service_account_token_path_env);
489
490            let service_account_token = if service_account_token_path.exists() {
491                let token_string = read_to_string(&service_account_token_path).map_err(|err| {
492                    error!(
493                        ?err,
494                        "Unable to open and read service account token file '{}'",
495                        service_account_token_path.display()
496                    );
497                    UnixIntegrationError
498                })?;
499
500                let token_string =
501                    token_string
502                        .lines()
503                        .next()
504                        .map(String::from)
505                        .ok_or_else(|| {
506                            error!(
507                                "Service account token file '{}' does not contain an api token",
508                                service_account_token_path.display()
509                            );
510                            UnixIntegrationError
511                        })?;
512
513                Some(token_string)
514            } else {
515                // The file does not exist, there is no token to use.
516                None
517            };
518
519            Some(KanidmConfig {
520                conn_timeout: kconfig.conn_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT),
521                request_timeout: kconfig.request_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT * 2),
522                pam_allowed_login_groups: kconfig.pam_allowed_login_groups.unwrap_or_default(),
523                map_group: kconfig.map_group,
524                service_account_token,
525            })
526        } else {
527            error!(
528                "You are using a version 2 config without a 'kanidm' section. USERS CANNOT AUTH."
529            );
530            None
531        };
532
533        // Now map the values into our config.
534        Ok(UnixdConfig {
535            cache_db_path: config.cache_db_path.unwrap_or(self.cache_db_path),
536            sock_path: config.sock_path.unwrap_or(self.sock_path),
537            task_sock_path: config.task_sock_path.unwrap_or(self.task_sock_path),
538            unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
539            cache_timeout: config.cache_timeout.unwrap_or(self.cache_timeout),
540            default_shell: config.default_shell.unwrap_or(self.default_shell),
541            home_prefix: config
542                .home_prefix
543                .map(|p| p.into())
544                .unwrap_or(self.home_prefix.clone()),
545            home_mount_prefix: config.home_mount_prefix.map(|p| p.into()),
546            home_attr: config
547                .home_attr
548                .and_then(|v| match v.as_str() {
549                    "uuid" => Some(HomeAttr::Uuid),
550                    "spn" => Some(HomeAttr::Spn),
551                    "name" => Some(HomeAttr::Name),
552                    _ => {
553                        warn!("Invalid home_attr configured, using default ...");
554                        None
555                    }
556                })
557                .unwrap_or(self.home_attr),
558            home_alias: config
559                .home_alias
560                .and_then(|v| match v.as_str() {
561                    "none" => Some(None),
562                    "uuid" => Some(Some(HomeAttr::Uuid)),
563                    "spn" => Some(Some(HomeAttr::Spn)),
564                    "name" => Some(Some(HomeAttr::Name)),
565                    _ => {
566                        warn!("Invalid home_alias configured, using default ...");
567                        None
568                    }
569                })
570                .unwrap_or(self.home_alias),
571            home_strategy: match config.home_strategy {
572                HomeStrategyV2::Symlink => HomeStrategy::Symlink,
573            },
574            use_etc_skel: config.use_etc_skel.unwrap_or(self.use_etc_skel),
575            uid_attr_map: config
576                .uid_attr_map
577                .and_then(|v| match v.as_str() {
578                    "spn" => Some(UidAttr::Spn),
579                    "name" => Some(UidAttr::Name),
580                    _ => {
581                        warn!("Invalid uid_attr_map configured, using default ...");
582                        None
583                    }
584                })
585                .unwrap_or(self.uid_attr_map),
586            gid_attr_map: config
587                .gid_attr_map
588                .and_then(|v| match v.as_str() {
589                    "spn" => Some(UidAttr::Spn),
590                    "name" => Some(UidAttr::Name),
591                    _ => {
592                        warn!("Invalid gid_attr_map configured, using default ...");
593                        None
594                    }
595                })
596                .unwrap_or(self.gid_attr_map),
597            selinux: match config.selinux.unwrap_or(self.selinux) {
598                #[cfg(all(target_family = "unix", feature = "selinux"))]
599                true => selinux_util::supported(),
600                _ => false,
601            },
602            hsm_pin_path: config.hsm_pin_path.unwrap_or(self.hsm_pin_path),
603            hsm_type: config
604                .hsm_type
605                .and_then(|v| match v.as_str() {
606                    "soft" => Some(HsmType::Soft),
607                    "tpm_if_possible" => Some(HsmType::TpmIfPossible),
608                    "tpm" => Some(HsmType::Tpm),
609                    _ => {
610                        warn!("Invalid hsm_type configured, using default ...");
611                        None
612                    }
613                })
614                .unwrap_or(self.hsm_type),
615            tpm_tcti_name: config
616                .tpm_tcti_name
617                .unwrap_or(DEFAULT_TPM_TCTI_NAME.to_string()),
618            kanidm_config,
619        })
620    }
621}
622
623#[derive(Debug)]
624/// This is the parsed configuration that will be used by pam/nss tools that need fast access to
625/// only the socket and timeout information related to the resolver.
626pub struct PamNssConfig {
627    pub sock_path: String,
628    // pub conn_timeout: u64,
629    pub unix_sock_timeout: u64,
630}
631
632impl Default for PamNssConfig {
633    fn default() -> Self {
634        PamNssConfig::new()
635    }
636}
637
638impl Display for PamNssConfig {
639    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
640        writeln!(f, "sock_path: {}", self.sock_path)?;
641        writeln!(f, "unix_sock_timeout: {}", self.unix_sock_timeout)
642    }
643}
644
645impl PamNssConfig {
646    pub fn new() -> Self {
647        PamNssConfig {
648            sock_path: DEFAULT_SOCK_PATH.to_string(),
649            unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
650        }
651    }
652
653    pub fn read_options_from_optional_config<P: AsRef<Path> + std::fmt::Debug>(
654        self,
655        config_path: P,
656    ) -> Result<Self, UnixIntegrationError> {
657        debug!("Attempting to load configuration from {:#?}", &config_path);
658        let mut f = match File::open(&config_path) {
659            Ok(f) => {
660                debug!("Successfully opened configuration file {:#?}", &config_path);
661                f
662            }
663            Err(e) => {
664                match e.kind() {
665                    ErrorKind::NotFound => {
666                        debug!(
667                            "Configuration file {:#?} not found, skipping.",
668                            &config_path
669                        );
670                    }
671                    ErrorKind::PermissionDenied => {
672                        warn!(
673                            "Permission denied loading configuration file {:#?}, skipping.",
674                            &config_path
675                        );
676                    }
677                    _ => {
678                        debug!(
679                            "Unable to open config file {:#?} [{:?}], skipping ...",
680                            &config_path, e
681                        );
682                    }
683                };
684                return Ok(self);
685            }
686        };
687
688        let mut contents = String::new();
689        f.read_to_string(&mut contents).map_err(|e| {
690            error!("{:?}", e);
691            UnixIntegrationError
692        })?;
693
694        let config: ConfigUntagged = toml::from_str(contents.as_str()).map_err(|e| {
695            error!("{:?}", e);
696            UnixIntegrationError
697        })?;
698
699        match config {
700            ConfigUntagged::Legacy(config) => self.apply_from_config_legacy(config),
701            ConfigUntagged::Versioned(ConfigVersion::V2 { values }) => {
702                self.apply_from_config_v2(values)
703            }
704        }
705    }
706
707    fn apply_from_config_legacy(self, config: ConfigInt) -> Result<Self, UnixIntegrationError> {
708        let unix_sock_timeout = config
709            .conn_timeout
710            .map(|v| v * 2)
711            .unwrap_or(self.unix_sock_timeout);
712
713        // Now map the values into our config.
714        Ok(PamNssConfig {
715            sock_path: config.sock_path.unwrap_or(self.sock_path),
716            unix_sock_timeout,
717        })
718    }
719
720    fn apply_from_config_v2(self, config: ConfigV2) -> Result<Self, UnixIntegrationError> {
721        let kanidm_conn_timeout = config
722            .kanidm
723            .as_ref()
724            .and_then(|k_config| k_config.conn_timeout)
725            .map(|timeout| timeout * 2);
726
727        // Now map the values into our config.
728        Ok(PamNssConfig {
729            sock_path: config.sock_path.unwrap_or(self.sock_path),
730            unix_sock_timeout: kanidm_conn_timeout.unwrap_or(self.unix_sock_timeout),
731        })
732    }
733}
734
735#[cfg(test)]
736mod tests {
737    use std::path::PathBuf;
738
739    use super::*;
740
741    #[test]
742    fn test_load_example_configs() {
743        // Test the various included configs
744
745        let examples_dir = env!("CARGO_MANIFEST_DIR").to_string() + "/../../examples/";
746
747        for file in PathBuf::from(&examples_dir)
748            .canonicalize()
749            .unwrap_or_else(|_| panic!("Can't find examples dir at {examples_dir}"))
750            .read_dir()
751            .expect("Can't read examples dir!")
752        {
753            let file = file.unwrap();
754            let filename = file.file_name().into_string().unwrap();
755            if filename.starts_with("unixd") {
756                print!("Checking that {filename} parses as a valid config...");
757
758                UnixdConfig::new()
759                    .read_options_from_optional_config(file.path())
760                    .inspect_err(|e| {
761                        println!("Failed to parse: {e:?}");
762                    })
763                    .expect("Failed to parse!");
764                println!("OK");
765            }
766        }
767    }
768}