1use 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#[allow(clippy::large_enum_variant)]
82#[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)]
109struct 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)]
153struct 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 #[serde(default)]
181 cache_db_path: Option<toml::value::Value>,
182 #[serde(default)]
183 kanidm: Option<toml::value::Value>,
184}
185
186#[derive(Debug)]
189pub 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)]
206pub 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 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 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 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)]
624pub struct PamNssConfig {
627 pub sock_path: String,
628 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 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 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 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}