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 BindMount,
106}
107
108#[derive(Debug, Deserialize)]
109#[serde(deny_unknown_fields)]
110struct 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)]
154struct 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 #[serde(default)]
182 cache_db_path: Option<toml::value::Value>,
183 #[serde(default)]
184 kanidm: Option<toml::value::Value>,
185}
186
187#[derive(Debug)]
190pub 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)]
209pub 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 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 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 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)]
635pub struct PamNssConfig {
638 pub sock_path: String,
639 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 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 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 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}