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