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