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)]
101#[serde(deny_unknown_fields)]
102struct ConfigV2 {
104 cache_db_path: Option<String>,
105 sock_path: Option<String>,
106 task_sock_path: Option<String>,
107
108 cache_timeout: Option<u64>,
109
110 default_shell: Option<String>,
111 home_prefix: Option<String>,
112 home_mount_prefix: Option<String>,
113 home_attr: Option<String>,
114 home_alias: Option<String>,
115 use_etc_skel: Option<bool>,
116 uid_attr_map: Option<String>,
117 gid_attr_map: Option<String>,
118 selinux: Option<bool>,
119
120 hsm_pin_path: Option<String>,
121 hsm_type: Option<String>,
122 tpm_tcti_name: Option<String>,
123
124 kanidm: Option<KanidmConfigV2>,
125}
126
127#[derive(Clone, Debug, Deserialize)]
128pub struct GroupMap {
129 pub local: String,
130 pub with: String,
131}
132
133#[derive(Debug, Deserialize)]
134struct KanidmConfigV2 {
135 conn_timeout: Option<u64>,
136 request_timeout: Option<u64>,
137 pam_allowed_login_groups: Option<Vec<String>>,
138 #[serde(default)]
139 map_group: Vec<GroupMap>,
140 service_account_token_path: Option<PathBuf>,
141}
142
143#[derive(Debug, Deserialize)]
144struct ConfigInt {
146 db_path: Option<String>,
147 sock_path: Option<String>,
148 task_sock_path: Option<String>,
149 conn_timeout: Option<u64>,
150 request_timeout: Option<u64>,
151 cache_timeout: Option<u64>,
152 pam_allowed_login_groups: Option<Vec<String>>,
153 default_shell: Option<String>,
154 home_prefix: Option<String>,
155 home_mount_prefix: Option<String>,
156 home_attr: Option<String>,
157 home_alias: Option<String>,
158 use_etc_skel: Option<bool>,
159 uid_attr_map: Option<String>,
160 gid_attr_map: Option<String>,
161 selinux: Option<bool>,
162 #[serde(default)]
163 allow_local_account_override: Vec<String>,
164
165 hsm_pin_path: Option<String>,
166 hsm_type: Option<String>,
167 tpm_tcti_name: Option<String>,
168
169 #[serde(default)]
172 cache_db_path: Option<toml::value::Value>,
173 #[serde(default)]
174 kanidm: Option<toml::value::Value>,
175}
176
177#[derive(Debug)]
180pub struct KanidmConfig {
183 pub conn_timeout: u64,
184 pub request_timeout: u64,
185 pub pam_allowed_login_groups: Vec<String>,
186 pub map_group: Vec<GroupMap>,
187 pub service_account_token: Option<String>,
188}
189
190#[derive(Debug)]
191pub 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 service_account_token: None,
372 });
373
374 Ok(UnixdConfig {
376 cache_db_path: config.db_path.unwrap_or(self.cache_db_path),
377 sock_path: config.sock_path.unwrap_or(self.sock_path),
378 task_sock_path: config.task_sock_path.unwrap_or(self.task_sock_path),
379 unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
380 cache_timeout: config.cache_timeout.unwrap_or(self.cache_timeout),
381 default_shell: config.default_shell.unwrap_or(self.default_shell),
382 home_prefix: config
383 .home_prefix
384 .map(|p| p.into())
385 .unwrap_or(self.home_prefix.clone()),
386 home_mount_prefix: config.home_mount_prefix.map(|p| p.into()),
387 home_attr: config
388 .home_attr
389 .and_then(|v| match v.as_str() {
390 "uuid" => Some(HomeAttr::Uuid),
391 "spn" => Some(HomeAttr::Spn),
392 "name" => Some(HomeAttr::Name),
393 _ => {
394 warn!("Invalid home_attr configured, using default ...");
395 None
396 }
397 })
398 .unwrap_or(self.home_attr),
399 home_alias: config
400 .home_alias
401 .and_then(|v| match v.as_str() {
402 "none" => Some(None),
403 "uuid" => Some(Some(HomeAttr::Uuid)),
404 "spn" => Some(Some(HomeAttr::Spn)),
405 "name" => Some(Some(HomeAttr::Name)),
406 _ => {
407 warn!("Invalid home_alias configured, using default ...");
408 None
409 }
410 })
411 .unwrap_or(self.home_alias),
412 use_etc_skel: config.use_etc_skel.unwrap_or(self.use_etc_skel),
413 uid_attr_map: config
414 .uid_attr_map
415 .and_then(|v| match v.as_str() {
416 "spn" => Some(UidAttr::Spn),
417 "name" => Some(UidAttr::Name),
418 _ => {
419 warn!("Invalid uid_attr_map configured, using default ...");
420 None
421 }
422 })
423 .unwrap_or(self.uid_attr_map),
424 gid_attr_map: config
425 .gid_attr_map
426 .and_then(|v| match v.as_str() {
427 "spn" => Some(UidAttr::Spn),
428 "name" => Some(UidAttr::Name),
429 _ => {
430 warn!("Invalid gid_attr_map configured, using default ...");
431 None
432 }
433 })
434 .unwrap_or(self.gid_attr_map),
435 selinux: match config.selinux.unwrap_or(self.selinux) {
436 #[cfg(all(target_family = "unix", feature = "selinux"))]
437 true => selinux_util::supported(),
438 _ => false,
439 },
440 hsm_pin_path: config.hsm_pin_path.unwrap_or(self.hsm_pin_path),
441 hsm_type: config
442 .hsm_type
443 .and_then(|v| match v.as_str() {
444 "soft" => Some(HsmType::Soft),
445 "tpm_if_possible" => Some(HsmType::TpmIfPossible),
446 "tpm" => Some(HsmType::Tpm),
447 _ => {
448 warn!("Invalid hsm_type configured, using default ...");
449 None
450 }
451 })
452 .unwrap_or(self.hsm_type),
453 tpm_tcti_name: config
454 .tpm_tcti_name
455 .unwrap_or(DEFAULT_TPM_TCTI_NAME.to_string()),
456 kanidm_config,
457 })
458 }
459
460 fn apply_from_config_v2(self, config: ConfigV2) -> Result<Self, UnixIntegrationError> {
461 let kanidm_config = if let Some(kconfig) = config.kanidm {
462 let service_account_token_path_env = match env::var("KANIDM_SERVICE_ACCOUNT_TOKEN_PATH")
463 {
464 Ok(val) => val.into(),
465 Err(_) => DEFAULT_KANIDM_SERVICE_ACCOUNT_TOKEN_PATH.into(),
466 };
467
468 let service_account_token_path: PathBuf = kconfig
469 .service_account_token_path
470 .unwrap_or(service_account_token_path_env);
471
472 let service_account_token = if service_account_token_path.exists() {
473 let token_string = read_to_string(&service_account_token_path).map_err(|err| {
474 error!(
475 ?err,
476 "Unable to open and read service account token file '{}'",
477 service_account_token_path.display()
478 );
479 UnixIntegrationError
480 })?;
481
482 let token_string =
483 token_string
484 .lines()
485 .next()
486 .map(String::from)
487 .ok_or_else(|| {
488 error!(
489 "Service account token file '{}' does not contain an api token",
490 service_account_token_path.display()
491 );
492 UnixIntegrationError
493 })?;
494
495 Some(token_string)
496 } else {
497 None
499 };
500
501 Some(KanidmConfig {
502 conn_timeout: kconfig.conn_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT),
503 request_timeout: kconfig.request_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT * 2),
504 pam_allowed_login_groups: kconfig.pam_allowed_login_groups.unwrap_or_default(),
505 map_group: kconfig.map_group,
506 service_account_token,
507 })
508 } else {
509 error!(
510 "You are using a version 2 config without a 'kanidm' section. USERS CANNOT AUTH."
511 );
512 None
513 };
514
515 Ok(UnixdConfig {
517 cache_db_path: config.cache_db_path.unwrap_or(self.cache_db_path),
518 sock_path: config.sock_path.unwrap_or(self.sock_path),
519 task_sock_path: config.task_sock_path.unwrap_or(self.task_sock_path),
520 unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
521 cache_timeout: config.cache_timeout.unwrap_or(self.cache_timeout),
522 default_shell: config.default_shell.unwrap_or(self.default_shell),
523 home_prefix: config
524 .home_prefix
525 .map(|p| p.into())
526 .unwrap_or(self.home_prefix.clone()),
527 home_mount_prefix: config.home_mount_prefix.map(|p| p.into()),
528 home_attr: config
529 .home_attr
530 .and_then(|v| match v.as_str() {
531 "uuid" => Some(HomeAttr::Uuid),
532 "spn" => Some(HomeAttr::Spn),
533 "name" => Some(HomeAttr::Name),
534 _ => {
535 warn!("Invalid home_attr configured, using default ...");
536 None
537 }
538 })
539 .unwrap_or(self.home_attr),
540 home_alias: config
541 .home_alias
542 .and_then(|v| match v.as_str() {
543 "none" => Some(None),
544 "uuid" => Some(Some(HomeAttr::Uuid)),
545 "spn" => Some(Some(HomeAttr::Spn)),
546 "name" => Some(Some(HomeAttr::Name)),
547 _ => {
548 warn!("Invalid home_alias configured, using default ...");
549 None
550 }
551 })
552 .unwrap_or(self.home_alias),
553 use_etc_skel: config.use_etc_skel.unwrap_or(self.use_etc_skel),
554 uid_attr_map: config
555 .uid_attr_map
556 .and_then(|v| match v.as_str() {
557 "spn" => Some(UidAttr::Spn),
558 "name" => Some(UidAttr::Name),
559 _ => {
560 warn!("Invalid uid_attr_map configured, using default ...");
561 None
562 }
563 })
564 .unwrap_or(self.uid_attr_map),
565 gid_attr_map: config
566 .gid_attr_map
567 .and_then(|v| match v.as_str() {
568 "spn" => Some(UidAttr::Spn),
569 "name" => Some(UidAttr::Name),
570 _ => {
571 warn!("Invalid gid_attr_map configured, using default ...");
572 None
573 }
574 })
575 .unwrap_or(self.gid_attr_map),
576 selinux: match config.selinux.unwrap_or(self.selinux) {
577 #[cfg(all(target_family = "unix", feature = "selinux"))]
578 true => selinux_util::supported(),
579 _ => false,
580 },
581 hsm_pin_path: config.hsm_pin_path.unwrap_or(self.hsm_pin_path),
582 hsm_type: config
583 .hsm_type
584 .and_then(|v| match v.as_str() {
585 "soft" => Some(HsmType::Soft),
586 "tpm_if_possible" => Some(HsmType::TpmIfPossible),
587 "tpm" => Some(HsmType::Tpm),
588 _ => {
589 warn!("Invalid hsm_type configured, using default ...");
590 None
591 }
592 })
593 .unwrap_or(self.hsm_type),
594 tpm_tcti_name: config
595 .tpm_tcti_name
596 .unwrap_or(DEFAULT_TPM_TCTI_NAME.to_string()),
597 kanidm_config,
598 })
599 }
600}
601
602#[derive(Debug)]
603pub struct PamNssConfig {
606 pub sock_path: String,
607 pub unix_sock_timeout: u64,
609}
610
611impl Default for PamNssConfig {
612 fn default() -> Self {
613 PamNssConfig::new()
614 }
615}
616
617impl Display for PamNssConfig {
618 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
619 writeln!(f, "sock_path: {}", self.sock_path)?;
620 writeln!(f, "unix_sock_timeout: {}", self.unix_sock_timeout)
621 }
622}
623
624impl PamNssConfig {
625 pub fn new() -> Self {
626 PamNssConfig {
627 sock_path: DEFAULT_SOCK_PATH.to_string(),
628 unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
629 }
630 }
631
632 pub fn read_options_from_optional_config<P: AsRef<Path> + std::fmt::Debug>(
633 self,
634 config_path: P,
635 ) -> Result<Self, UnixIntegrationError> {
636 debug!("Attempting to load configuration from {:#?}", &config_path);
637 let mut f = match File::open(&config_path) {
638 Ok(f) => {
639 debug!("Successfully opened configuration file {:#?}", &config_path);
640 f
641 }
642 Err(e) => {
643 match e.kind() {
644 ErrorKind::NotFound => {
645 debug!(
646 "Configuration file {:#?} not found, skipping.",
647 &config_path
648 );
649 }
650 ErrorKind::PermissionDenied => {
651 warn!(
652 "Permission denied loading configuration file {:#?}, skipping.",
653 &config_path
654 );
655 }
656 _ => {
657 debug!(
658 "Unable to open config file {:#?} [{:?}], skipping ...",
659 &config_path, e
660 );
661 }
662 };
663 return Ok(self);
664 }
665 };
666
667 let mut contents = String::new();
668 f.read_to_string(&mut contents).map_err(|e| {
669 error!("{:?}", e);
670 UnixIntegrationError
671 })?;
672
673 let config: ConfigUntagged = toml::from_str(contents.as_str()).map_err(|e| {
674 error!("{:?}", e);
675 UnixIntegrationError
676 })?;
677
678 match config {
679 ConfigUntagged::Legacy(config) => self.apply_from_config_legacy(config),
680 ConfigUntagged::Versioned(ConfigVersion::V2 { values }) => {
681 self.apply_from_config_v2(values)
682 }
683 }
684 }
685
686 fn apply_from_config_legacy(self, config: ConfigInt) -> Result<Self, UnixIntegrationError> {
687 let unix_sock_timeout = config
688 .conn_timeout
689 .map(|v| v * 2)
690 .unwrap_or(self.unix_sock_timeout);
691
692 Ok(PamNssConfig {
694 sock_path: config.sock_path.unwrap_or(self.sock_path),
695 unix_sock_timeout,
696 })
697 }
698
699 fn apply_from_config_v2(self, config: ConfigV2) -> Result<Self, UnixIntegrationError> {
700 let kanidm_conn_timeout = config
701 .kanidm
702 .as_ref()
703 .and_then(|k_config| k_config.conn_timeout)
704 .map(|timeout| timeout * 2);
705
706 Ok(PamNssConfig {
708 sock_path: config.sock_path.unwrap_or(self.sock_path),
709 unix_sock_timeout: kanidm_conn_timeout.unwrap_or(self.unix_sock_timeout),
710 })
711 }
712}
713
714#[cfg(test)]
715mod tests {
716 use std::path::PathBuf;
717
718 use super::*;
719
720 #[test]
721 fn test_load_example_configs() {
722 let examples_dir = env!("CARGO_MANIFEST_DIR").to_string() + "/../../examples/";
725
726 for file in PathBuf::from(&examples_dir)
727 .canonicalize()
728 .unwrap_or_else(|_| panic!("Can't find examples dir at {examples_dir}"))
729 .read_dir()
730 .expect("Can't read examples dir!")
731 {
732 let file = file.unwrap();
733 let filename = file.file_name().into_string().unwrap();
734 if filename.starts_with("unixd") {
735 print!("Checking that {filename} parses as a valid config...");
736
737 UnixdConfig::new()
738 .read_options_from_optional_config(file.path())
739 .inspect_err(|e| {
740 println!("Failed to parse: {e:?}");
741 })
742 .expect("Failed to parse!");
743 println!("OK");
744 }
745 }
746 }
747}