1use 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#[allow(clippy::large_enum_variant)]
84#[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)]
104struct 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)]
145struct 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 #[serde(default)]
173 cache_db_path: Option<toml::value::Value>,
174 #[serde(default)]
175 kanidm: Option<toml::value::Value>,
176}
177
178#[derive(Debug)]
181pub 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)]
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 });
372
373 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 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)]
572pub struct PamNssConfig {
575 pub sock_path: String,
576 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 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 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 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}