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)]
101#[serde(deny_unknown_fields)]
102/// This is the version 2 of the JSON configuration specification for the unixd suite.
103struct 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)]
144/// This is the version 1 of the JSON configuration specification for the unixd suite.
145struct 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    // Detect and warn on values in these places - this is to catch
170    // when someone is using a v2 value on a v1 config.
171    #[serde(default)]
172    cache_db_path: Option<toml::value::Value>,
173    #[serde(default)]
174    kanidm: Option<toml::value::Value>,
175}
176
177// ========================================================================
178
179#[derive(Debug)]
180/// This is the parsed Kanidm provider configuration that the Unixd resolver
181/// will use to connect to Kanidm.
182pub 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)]
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            service_account_token: None,
372        });
373
374        // Now map the values into our config.
375        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            match &kconfig.pam_allowed_login_groups {
463                None => {
464                    error!("You have a 'kanidm' section in the config but an empty pam_allowed_login_groups set. USERS CANNOT AUTH.")
465                }
466                Some(groups) => {
467                    if groups.is_empty() {
468                        error!("You have a 'kanidm' section in the config but an empty pam_allowed_login_groups set. USERS CANNOT AUTH.");
469                    }
470                }
471            }
472
473            let service_account_token_path_env = match env::var("KANIDM_SERVICE_ACCOUNT_TOKEN_PATH")
474            {
475                Ok(val) => val.into(),
476                Err(_) => DEFAULT_KANIDM_SERVICE_ACCOUNT_TOKEN_PATH.into(),
477            };
478
479            let service_account_token_path: PathBuf = kconfig
480                .service_account_token_path
481                .unwrap_or(service_account_token_path_env);
482
483            let service_account_token = if service_account_token_path.exists() {
484                let token_string = read_to_string(&service_account_token_path).map_err(|err| {
485                    error!(
486                        ?err,
487                        "Unable to open and read service account token file '{}'",
488                        service_account_token_path.display()
489                    );
490                    UnixIntegrationError
491                })?;
492
493                let token_string =
494                    token_string
495                        .lines()
496                        .next()
497                        .map(String::from)
498                        .ok_or_else(|| {
499                            error!(
500                                "Service account token file '{}' does not contain an api token",
501                                service_account_token_path.display()
502                            );
503                            UnixIntegrationError
504                        })?;
505
506                Some(token_string)
507            } else {
508                // The file does not exist, there is no token to use.
509                None
510            };
511
512            Some(KanidmConfig {
513                conn_timeout: kconfig.conn_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT),
514                request_timeout: kconfig.request_timeout.unwrap_or(DEFAULT_CONN_TIMEOUT * 2),
515                pam_allowed_login_groups: kconfig.pam_allowed_login_groups.unwrap_or_default(),
516                map_group: kconfig.map_group,
517                service_account_token,
518            })
519        } else {
520            error!(
521                "You are using a version 2 config without a 'kanidm' section. USERS CANNOT AUTH."
522            );
523            None
524        };
525
526        // Now map the values into our config.
527        Ok(UnixdConfig {
528            cache_db_path: config.cache_db_path.unwrap_or(self.cache_db_path),
529            sock_path: config.sock_path.unwrap_or(self.sock_path),
530            task_sock_path: config.task_sock_path.unwrap_or(self.task_sock_path),
531            unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
532            cache_timeout: config.cache_timeout.unwrap_or(self.cache_timeout),
533            default_shell: config.default_shell.unwrap_or(self.default_shell),
534            home_prefix: config
535                .home_prefix
536                .map(|p| p.into())
537                .unwrap_or(self.home_prefix.clone()),
538            home_mount_prefix: config.home_mount_prefix.map(|p| p.into()),
539            home_attr: config
540                .home_attr
541                .and_then(|v| match v.as_str() {
542                    "uuid" => Some(HomeAttr::Uuid),
543                    "spn" => Some(HomeAttr::Spn),
544                    "name" => Some(HomeAttr::Name),
545                    _ => {
546                        warn!("Invalid home_attr configured, using default ...");
547                        None
548                    }
549                })
550                .unwrap_or(self.home_attr),
551            home_alias: config
552                .home_alias
553                .and_then(|v| match v.as_str() {
554                    "none" => Some(None),
555                    "uuid" => Some(Some(HomeAttr::Uuid)),
556                    "spn" => Some(Some(HomeAttr::Spn)),
557                    "name" => Some(Some(HomeAttr::Name)),
558                    _ => {
559                        warn!("Invalid home_alias configured, using default ...");
560                        None
561                    }
562                })
563                .unwrap_or(self.home_alias),
564            use_etc_skel: config.use_etc_skel.unwrap_or(self.use_etc_skel),
565            uid_attr_map: config
566                .uid_attr_map
567                .and_then(|v| match v.as_str() {
568                    "spn" => Some(UidAttr::Spn),
569                    "name" => Some(UidAttr::Name),
570                    _ => {
571                        warn!("Invalid uid_attr_map configured, using default ...");
572                        None
573                    }
574                })
575                .unwrap_or(self.uid_attr_map),
576            gid_attr_map: config
577                .gid_attr_map
578                .and_then(|v| match v.as_str() {
579                    "spn" => Some(UidAttr::Spn),
580                    "name" => Some(UidAttr::Name),
581                    _ => {
582                        warn!("Invalid gid_attr_map configured, using default ...");
583                        None
584                    }
585                })
586                .unwrap_or(self.gid_attr_map),
587            selinux: match config.selinux.unwrap_or(self.selinux) {
588                #[cfg(all(target_family = "unix", feature = "selinux"))]
589                true => selinux_util::supported(),
590                _ => false,
591            },
592            hsm_pin_path: config.hsm_pin_path.unwrap_or(self.hsm_pin_path),
593            hsm_type: config
594                .hsm_type
595                .and_then(|v| match v.as_str() {
596                    "soft" => Some(HsmType::Soft),
597                    "tpm_if_possible" => Some(HsmType::TpmIfPossible),
598                    "tpm" => Some(HsmType::Tpm),
599                    _ => {
600                        warn!("Invalid hsm_type configured, using default ...");
601                        None
602                    }
603                })
604                .unwrap_or(self.hsm_type),
605            tpm_tcti_name: config
606                .tpm_tcti_name
607                .unwrap_or(DEFAULT_TPM_TCTI_NAME.to_string()),
608            kanidm_config,
609        })
610    }
611}
612
613#[derive(Debug)]
614/// This is the parsed configuration that will be used by pam/nss tools that need fast access to
615/// only the socket and timeout information related to the resolver.
616pub struct PamNssConfig {
617    pub sock_path: String,
618    // pub conn_timeout: u64,
619    pub unix_sock_timeout: u64,
620}
621
622impl Default for PamNssConfig {
623    fn default() -> Self {
624        PamNssConfig::new()
625    }
626}
627
628impl Display for PamNssConfig {
629    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
630        writeln!(f, "sock_path: {}", self.sock_path)?;
631        writeln!(f, "unix_sock_timeout: {}", self.unix_sock_timeout)
632    }
633}
634
635impl PamNssConfig {
636    pub fn new() -> Self {
637        PamNssConfig {
638            sock_path: DEFAULT_SOCK_PATH.to_string(),
639            unix_sock_timeout: DEFAULT_CONN_TIMEOUT * 2,
640        }
641    }
642
643    pub fn read_options_from_optional_config<P: AsRef<Path> + std::fmt::Debug>(
644        self,
645        config_path: P,
646    ) -> Result<Self, UnixIntegrationError> {
647        debug!("Attempting to load configuration from {:#?}", &config_path);
648        let mut f = match File::open(&config_path) {
649            Ok(f) => {
650                debug!("Successfully opened configuration file {:#?}", &config_path);
651                f
652            }
653            Err(e) => {
654                match e.kind() {
655                    ErrorKind::NotFound => {
656                        debug!(
657                            "Configuration file {:#?} not found, skipping.",
658                            &config_path
659                        );
660                    }
661                    ErrorKind::PermissionDenied => {
662                        warn!(
663                            "Permission denied loading configuration file {:#?}, skipping.",
664                            &config_path
665                        );
666                    }
667                    _ => {
668                        debug!(
669                            "Unable to open config file {:#?} [{:?}], skipping ...",
670                            &config_path, e
671                        );
672                    }
673                };
674                return Ok(self);
675            }
676        };
677
678        let mut contents = String::new();
679        f.read_to_string(&mut contents).map_err(|e| {
680            error!("{:?}", e);
681            UnixIntegrationError
682        })?;
683
684        let config: ConfigUntagged = toml::from_str(contents.as_str()).map_err(|e| {
685            error!("{:?}", e);
686            UnixIntegrationError
687        })?;
688
689        match config {
690            ConfigUntagged::Legacy(config) => self.apply_from_config_legacy(config),
691            ConfigUntagged::Versioned(ConfigVersion::V2 { values }) => {
692                self.apply_from_config_v2(values)
693            }
694        }
695    }
696
697    fn apply_from_config_legacy(self, config: ConfigInt) -> Result<Self, UnixIntegrationError> {
698        let unix_sock_timeout = config
699            .conn_timeout
700            .map(|v| v * 2)
701            .unwrap_or(self.unix_sock_timeout);
702
703        // Now map the values into our config.
704        Ok(PamNssConfig {
705            sock_path: config.sock_path.unwrap_or(self.sock_path),
706            unix_sock_timeout,
707        })
708    }
709
710    fn apply_from_config_v2(self, config: ConfigV2) -> Result<Self, UnixIntegrationError> {
711        let kanidm_conn_timeout = config
712            .kanidm
713            .as_ref()
714            .and_then(|k_config| k_config.conn_timeout)
715            .map(|timeout| timeout * 2);
716
717        // Now map the values into our config.
718        Ok(PamNssConfig {
719            sock_path: config.sock_path.unwrap_or(self.sock_path),
720            unix_sock_timeout: kanidm_conn_timeout.unwrap_or(self.unix_sock_timeout),
721        })
722    }
723}
724
725#[cfg(test)]
726mod tests {
727    use std::path::PathBuf;
728
729    use super::*;
730
731    #[test]
732    fn test_load_example_configs() {
733        // Test the various included configs
734
735        let examples_dir = env!("CARGO_MANIFEST_DIR").to_string() + "/../../examples/";
736
737        for file in PathBuf::from(&examples_dir)
738            .canonicalize()
739            .unwrap_or_else(|_| panic!("Can't find examples dir at {examples_dir}"))
740            .read_dir()
741            .expect("Can't read examples dir!")
742        {
743            let file = file.unwrap();
744            let filename = file.file_name().into_string().unwrap();
745            if filename.starts_with("unixd") {
746                print!("Checking that {filename} parses as a valid config...");
747
748                UnixdConfig::new()
749                    .read_options_from_optional_config(file.path())
750                    .inspect_err(|e| {
751                        println!("Failed to parse: {e:?}");
752                    })
753                    .expect("Failed to parse!");
754                println!("OK");
755            }
756        }
757    }
758}