1use std::collections::HashMap;
2#[cfg(feature = "finder")]
3use std::path::PathBuf;
4use std::{borrow::Borrow, cell::RefCell, rc::Rc};
5
6use bon::{bon, builder, Builder};
7use chrono::Duration;
8
9#[cfg(feature = "finder")]
10use libc::PATH_MAX;
11use linked_hash_set::LinkedHashSet;
12
13#[cfg(feature = "pcre2")]
14use pcre2::bytes::Regex;
15use serde::{Deserialize, Deserializer, Serialize};
16use serde_json::{Map, Value};
17use strum::{Display, EnumIs, EnumIter, FromRepr};
18
19use log::debug;
20#[cfg(feature = "finder")]
21use log::warn;
22
23use crate::rc_refcell;
24
25#[cfg(feature = "finder")]
26use super::finder::Cred;
27use super::{deserialize_duration, is_default, serialize_duration, FilterMatcher};
28
29use super::{
30 lhs_deserialize, lhs_deserialize_envkey, lhs_serialize, lhs_serialize_envkey,
31 structs::{SConfig, SRole, STask},
32};
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
35pub enum Level {
36 #[default]
37 None,
38 Default,
39 Global,
40 Role,
41 Task,
42}
43
44#[derive(Debug, Clone, Copy, FromRepr, EnumIter, Display)]
45pub enum OptType {
46 Path,
47 Env,
48 Root,
49 Bounding,
50 Wildcard,
51 Timeout,
52}
53
54#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, EnumIs, Display, Clone, Copy)]
55#[serde(rename_all = "lowercase")]
56#[derive(Default)]
57pub enum PathBehavior {
58 Delete,
59 KeepSafe,
60 KeepUnsafe,
61 #[default]
62 Inherit,
63}
64
65#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, EnumIs, Clone, Copy, Display)]
66#[serde(rename_all = "lowercase")]
67#[derive(Default)]
68pub enum TimestampType {
69 #[default]
70 PPID,
71 TTY,
72 UID,
73}
74
75#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default, Builder)]
76pub struct STimeout {
77 #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
78 pub type_field: Option<TimestampType>,
79 #[serde(
80 serialize_with = "serialize_duration",
81 deserialize_with = "deserialize_duration",
82 skip_serializing_if = "Option::is_none"
83 )]
84 pub duration: Option<Duration>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub max_usage: Option<u64>,
87 #[serde(default)]
88 #[serde(flatten, skip_serializing_if = "Map::is_empty")]
89 #[builder(default)]
90 pub _extra_fields: Map<String, Value>,
91}
92
93#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Builder)]
94pub struct SPathOptions {
95 #[serde(rename = "default", default, skip_serializing_if = "is_default")]
96 #[builder(start_fn)]
97 pub default_behavior: PathBehavior,
98 #[serde(
99 default,
100 skip_serializing_if = "Option::is_none",
101 deserialize_with = "lhs_deserialize",
102 serialize_with = "lhs_serialize"
103 )]
104 #[builder(with = |v : impl IntoIterator<Item = impl ToString>| { v.into_iter().map(|s| s.to_string()).collect() })]
105 pub add: Option<LinkedHashSet<String>>,
106 #[serde(
107 default,
108 skip_serializing_if = "Option::is_none",
109 deserialize_with = "lhs_deserialize",
110 serialize_with = "lhs_serialize",
111 alias = "del"
112 )]
113 #[builder(with = |v : impl IntoIterator<Item = impl ToString>| { v.into_iter().map(|s| s.to_string()).collect() })]
114 pub sub: Option<LinkedHashSet<String>>,
115 #[serde(default)]
116 #[serde(flatten)]
117 #[builder(default)]
118 pub _extra_fields: Map<String, Value>,
119}
120
121#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, EnumIs, Display, Clone, Copy)]
122#[serde(rename_all = "lowercase")]
123#[derive(Default)]
124pub enum EnvBehavior {
125 Delete,
126 Keep,
127 #[default]
128 Inherit,
129}
130
131#[derive(Serialize, Hash, Deserialize, PartialEq, Eq, Debug, EnumIs, Clone)]
132enum EnvKeyType {
133 Wildcarded,
134 Normal,
135}
136
137#[derive(Eq, Hash, PartialEq, Serialize, Debug, Clone, Builder)]
138#[serde(transparent)]
139pub struct EnvKey {
140 #[serde(skip)]
141 env_type: EnvKeyType,
142 value: String,
143}
144
145impl std::fmt::Display for EnvKey {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 write!(f, "{}", self.value)
148 }
149}
150
151#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default, Builder)]
152pub struct SEnvOptions {
153 #[serde(rename = "default", default, skip_serializing_if = "is_default")]
154 #[builder(start_fn)]
155 pub default_behavior: EnvBehavior,
156 #[serde(alias = "override", default, skip_serializing_if = "Option::is_none")]
157 pub override_behavior: Option<bool>,
158 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
159 #[builder(default, with = |iter: impl IntoIterator<Item = (impl ToString, impl ToString)>| {
160 let mut map = HashMap::with_hasher(Default::default());
161 map.extend(iter.into_iter().map(|(k, v)| (k.to_string(), v.to_string())));
162 map
163 })]
164 pub set: HashMap<String, String>,
165 #[serde(
166 default,
167 skip_serializing_if = "Option::is_none",
168 deserialize_with = "lhs_deserialize_envkey",
169 serialize_with = "lhs_serialize_envkey"
170 )]
171 #[builder(with = |v : impl IntoIterator<Item = impl ToString>| -> Result<_,String> { let mut res = LinkedHashSet::new(); for s in v { res.insert(EnvKey::new(s.to_string())?); } Ok(res)})]
172 pub keep: Option<LinkedHashSet<EnvKey>>,
173 #[serde(
174 default,
175 skip_serializing_if = "Option::is_none",
176 deserialize_with = "lhs_deserialize_envkey",
177 serialize_with = "lhs_serialize_envkey"
178 )]
179 #[builder(with = |v : impl IntoIterator<Item = impl ToString>| -> Result<_,String> { let mut res = LinkedHashSet::new(); for s in v { res.insert(EnvKey::new(s.to_string())?); } Ok(res)})]
180 pub check: Option<LinkedHashSet<EnvKey>>,
181 #[serde(
182 default,
183 skip_serializing_if = "Option::is_none",
184 deserialize_with = "lhs_deserialize_envkey",
185 serialize_with = "lhs_serialize_envkey"
186 )]
187 #[builder(with = |v : impl IntoIterator<Item = impl ToString>| -> Result<_,String> { let mut res = LinkedHashSet::new(); for s in v { res.insert(EnvKey::new(s.to_string())?); } Ok(res)})]
188 pub delete: Option<LinkedHashSet<EnvKey>>,
189 #[serde(default, flatten)]
190 #[builder(default)]
191 pub _extra_fields: Map<String, Value>,
192}
193
194#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, EnumIs, Display, Clone, Copy)]
195#[serde(rename_all = "lowercase")]
196#[derive(Default)]
197pub enum SBounding {
198 Strict,
199 Ignore,
200 #[default]
201 Inherit,
202}
203
204#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, EnumIs, Display, Clone, Copy)]
205#[serde(rename_all = "kebab-case")]
206#[derive(Default)]
207pub enum SPrivileged {
208 Privileged,
209 #[default]
210 User,
211 Inherit,
212}
213
214#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, EnumIs, Display, Clone, Copy)]
215#[serde(rename_all = "kebab-case")]
216#[derive(Default)]
217pub enum SAuthentication {
218 Skip,
219 #[default]
220 Perform,
221 Inherit,
222}
223
224#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
225#[serde(rename_all = "kebab-case")]
226pub struct Opt {
227 #[serde(skip)]
228 pub level: Level,
229 #[serde(skip_serializing_if = "Option::is_none")]
230 pub path: Option<SPathOptions>,
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub env: Option<SEnvOptions>,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub root: Option<SPrivileged>,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub bounding: Option<SBounding>,
237 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub authentication: Option<SAuthentication>,
239 #[serde(skip_serializing_if = "Option::is_none")]
240 pub wildcard_denied: Option<String>,
241 #[serde(default, skip_serializing_if = "Option::is_none")]
242 pub timeout: Option<STimeout>,
243 #[serde(default, flatten)]
244 pub _extra_fields: Map<String, Value>,
245}
246
247#[bon]
248impl Opt {
249 #[builder]
250 pub fn new(
251 #[builder(start_fn)] level: Level,
252 path: Option<SPathOptions>,
253 env: Option<SEnvOptions>,
254 root: Option<SPrivileged>,
255 bounding: Option<SBounding>,
256 authentication: Option<SAuthentication>,
257 #[builder(into)] wildcard_denied: Option<String>,
258 timeout: Option<STimeout>,
259 #[builder(default)] _extra_fields: Map<String, Value>,
260 ) -> Rc<RefCell<Self>> {
261 rc_refcell!(Opt {
262 level,
263 path,
264 env,
265 root,
266 bounding,
267 authentication,
268 wildcard_denied,
269 timeout,
270 _extra_fields,
271 })
272 }
273
274 pub fn raw_new(level: Level) -> Self {
275 Opt {
276 level,
277 ..Default::default()
278 }
279 }
280
281 pub fn level_default() -> Rc<RefCell<Self>> {
282 Self::builder(Level::Default)
283 .root(SPrivileged::User)
284 .bounding(SBounding::Strict)
285 .path(
286 SPathOptions::builder(PathBehavior::Delete)
287 .add([
288 "/usr/local/sbin",
289 "/usr/local/bin",
290 "/usr/sbin",
291 "/usr/bin",
292 "/sbin",
293 "/snap/bin",
294 ])
295 .build(),
296 )
297 .authentication(SAuthentication::Perform)
298 .env(
299 SEnvOptions::builder(EnvBehavior::Delete)
300 .keep([
301 "HOME",
302 "USER",
303 "LOGNAME",
304 "COLORS",
305 "DISPLAY",
306 "HOSTNAME",
307 "KRB5CCNAME",
308 "LS_COLORS",
309 "PS1",
310 "PS2",
311 "XAUTHORY",
312 "XAUTHORIZATION",
313 "XDG_CURRENT_DESKTOP",
314 ])
315 .unwrap()
316 .check([
317 "COLORTERM",
318 "LANG",
319 "LANGUAGE",
320 "LC_*",
321 "LINGUAS",
322 "TERM",
323 "TZ",
324 ])
325 .unwrap()
326 .delete([
327 "PS4",
328 "SHELLOPTS",
329 "PERLLIB",
330 "PERL5LIB",
331 "PERL5OPT",
332 "PYTHONINSPECT",
333 ])
334 .unwrap()
335 .build(),
336 )
337 .timeout(
338 STimeout::builder()
339 .type_field(TimestampType::PPID)
340 .duration(Duration::minutes(5))
341 .build(),
342 )
343 .wildcard_denied(";&|")
344 .build()
345 }
346}
347
348impl Default for Opt {
349 fn default() -> Self {
350 Opt {
351 path: Some(SPathOptions::default()),
352 env: Some(SEnvOptions::default()),
353 root: Some(SPrivileged::default()),
354 bounding: Some(SBounding::default()),
355 authentication: None,
356 wildcard_denied: None,
357 timeout: None,
358 _extra_fields: Map::default(),
359 level: Level::Default,
360 }
361 }
362}
363
364impl Default for OptStack {
365 fn default() -> Self {
366 OptStack {
367 stack: [None, Some(Opt::level_default()), None, None, None],
368 roles: None,
369 role: None,
370 task: None,
371 }
372 }
373}
374
375impl Default for SPathOptions {
376 fn default() -> Self {
377 SPathOptions {
378 default_behavior: PathBehavior::Inherit,
379 add: None,
380 sub: None,
381 _extra_fields: Map::default(),
382 }
383 }
384}
385
386fn is_valid_env_name(s: &str) -> bool {
387 let mut chars = s.chars();
388
389 if let Some(first_char) = chars.next() {
391 if !(first_char.is_ascii_alphabetic() || first_char == '_') {
392 return false;
393 }
394 } else {
395 return false; }
397
398 chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
400}
401
402#[cfg(feature = "pcre2")]
403fn is_regex(s: &str) -> bool {
404 Regex::new(s).is_ok()
405}
406
407#[cfg(not(feature = "pcre2"))]
408fn is_regex(_s: &str) -> bool {
409 true }
411
412impl EnvKey {
413 pub fn new(s: String) -> Result<Self, String> {
414 if is_valid_env_name(&s) {
416 Ok(EnvKey {
417 env_type: EnvKeyType::Normal,
418 value: s,
419 })
420 } else if is_regex(&s) {
421 Ok(EnvKey {
422 env_type: EnvKeyType::Wildcarded,
423 value: s,
424 })
425 } else {
426 Err(format!(
427 "env key {}, must be a valid env, or a valid regex",
428 s
429 ))
430 }
431 }
432}
433
434impl PartialEq<str> for EnvKey {
435 fn eq(&self, other: &str) -> bool {
436 self.value == *other
437 }
438}
439
440impl From<EnvKey> for String {
441 fn from(val: EnvKey) -> Self {
442 val.value
443 }
444}
445
446impl From<String> for EnvKey {
447 fn from(s: String) -> Self {
448 EnvKey::new(s).expect("Invalid env key")
449 }
450}
451
452impl From<&str> for EnvKey {
453 fn from(s: &str) -> Self {
454 EnvKey::new(s.into()).expect("Invalid env key")
455 }
456}
457
458impl<'de> Deserialize<'de> for EnvKey {
459 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
460 where
461 D: Deserializer<'de>,
462 {
463 let s = String::deserialize(deserializer)?;
464 EnvKey::new(s).map_err(serde::de::Error::custom)
465 }
466}
467
468impl SEnvOptions {
469 pub fn new(behavior: EnvBehavior) -> Self {
470 SEnvOptions {
471 default_behavior: behavior,
472 ..Default::default()
473 }
474 }
475}
476
477trait EnvSet {
478 fn env_matches(&self, wildcarded: &EnvKey) -> bool;
479}
480
481impl<T> EnvSet for HashMap<String, T> {
482 fn env_matches(&self, wildcarded: &EnvKey) -> bool {
483 match wildcarded.env_type {
484 EnvKeyType::Normal => self.contains_key(&wildcarded.value),
485 EnvKeyType::Wildcarded => self.keys().any(|s| check_wildcarded(wildcarded, s)),
486 }
487 }
488}
489
490impl EnvSet for LinkedHashSet<EnvKey> {
491 fn env_matches(&self, needle: &EnvKey) -> bool {
492 self.iter().any(|s| match s.env_type {
493 EnvKeyType::Normal => s == needle,
494 EnvKeyType::Wildcarded => check_wildcarded(s, &needle.value),
495 })
496 }
497}
498
499impl EnvSet for Option<LinkedHashSet<EnvKey>> {
500 fn env_matches(&self, needle: &EnvKey) -> bool {
501 self.as_ref().map_or(false, |set| set.env_matches(needle))
502 }
503}
504
505#[cfg(feature = "pcre2")]
506fn check_wildcarded(wildcarded: &EnvKey, s: &String) -> bool {
507 Regex::new(&format!("^{}$", wildcarded.value)) .unwrap()
509 .is_match(s.as_bytes())
510 .is_ok_and(|m| m)
511}
512
513#[cfg(not(feature = "pcre2"))]
514fn check_wildcarded(_wildcarded: &EnvKey, _s: &String) -> bool {
515 true
516}
517
518#[cfg(feature = "finder")]
519fn tz_is_safe(tzval: &str) -> bool {
520 let tzval = if let Some(val) = tzval.strip_prefix(':') {
522 val
523 } else {
524 tzval
525 };
526
527 if tzval.starts_with('/') {
529 return false;
530 }
531
532 let mut lastch = '/';
535 for cp in tzval.chars() {
536 if cp.is_ascii_whitespace() || !cp.is_ascii_graphic() {
537 return false;
538 }
539 if lastch == '/'
540 && cp == '.'
541 && tzval
542 .chars()
543 .nth(tzval.chars().position(|c| c == '.').unwrap() + 1)
544 == Some('.')
545 && (tzval
546 .chars()
547 .nth(tzval.chars().position(|c| c == '.').unwrap() + 2)
548 == Some('/')
549 || tzval
550 .chars()
551 .nth(tzval.chars().position(|c| c == '.').unwrap() + 2)
552 .is_none())
553 {
554 return false;
555 }
556 lastch = cp;
557 }
558
559 if tzval.len() >= PATH_MAX.try_into().unwrap() {
561 return false;
562 }
563
564 true
565}
566
567#[cfg(feature = "finder")]
568fn check_env(key: &str, value: &str) -> bool {
569 debug!("Checking env: {}={}", key, value);
570 match key {
571 "TZ" => tz_is_safe(value),
572 _ => !value.chars().any(|c| c == '/' || c == '%'),
573 }
574}
575
576#[derive(Debug, Clone, Serialize, Deserialize)]
577pub struct OptStack {
578 pub(crate) stack: [Option<Rc<RefCell<Opt>>>; 5],
579 roles: Option<Rc<RefCell<SConfig>>>,
580 role: Option<Rc<RefCell<SRole>>>,
581 task: Option<Rc<RefCell<STask>>>,
582}
583
584#[cfg(not(tarpaulin_include))]
585impl<S: opt_stack_builder::State> OptStackBuilder<S> {
586 fn opt(mut self, opt: Option<Rc<RefCell<Opt>>>) -> Self {
587 if let Some(opt) = opt {
588 self.stack[opt.as_ref().borrow().level as usize] = Some(opt.clone());
589 }
590 self
591 }
592 fn with_task(
593 self,
594 task: Rc<RefCell<STask>>,
595 ) -> OptStackBuilder<
596 opt_stack_builder::SetTask<opt_stack_builder::SetRole<opt_stack_builder::SetRoles<S>>>,
597 >
598 where
599 <S as opt_stack_builder::State>::Roles: opt_stack_builder::IsUnset,
600 <S as opt_stack_builder::State>::Role: opt_stack_builder::IsUnset,
601 <S as opt_stack_builder::State>::Task: opt_stack_builder::IsUnset,
602 {
603 self.with_role(
604 task.as_ref()
605 .borrow()
606 ._role
607 .as_ref()
608 .unwrap()
609 .upgrade()
610 .unwrap(),
611 )
612 .task(task.to_owned())
613 .opt(task.as_ref().borrow().options.to_owned())
614 }
615 fn with_role(
616 self,
617 role: Rc<RefCell<SRole>>,
618 ) -> OptStackBuilder<opt_stack_builder::SetRole<opt_stack_builder::SetRoles<S>>>
619 where
620 <S as opt_stack_builder::State>::Roles: opt_stack_builder::IsUnset,
621 <S as opt_stack_builder::State>::Role: opt_stack_builder::IsUnset,
622 {
623 self.with_roles(
624 role.as_ref()
625 .borrow()
626 ._config
627 .as_ref()
628 .unwrap()
629 .upgrade()
630 .unwrap(),
631 )
632 .role(role.to_owned())
633 .opt(role.as_ref().borrow().options.to_owned())
634 }
635
636 fn with_roles(
637 self,
638 roles: Rc<RefCell<SConfig>>,
639 ) -> OptStackBuilder<opt_stack_builder::SetRoles<S>>
640 where
641 <S as opt_stack_builder::State>::Roles: opt_stack_builder::IsUnset,
642 {
643 self.with_default()
644 .roles(roles.to_owned())
645 .opt(roles.as_ref().borrow().options.to_owned())
646 }
647
648 fn with_default(self) -> Self {
649 self.opt(Some(
650 Opt::builder(Level::Default)
651 .root(SPrivileged::User)
652 .bounding(SBounding::Strict)
653 .path(
654 SPathOptions::builder(PathBehavior::Delete)
655 .add([
656 "/usr/local/sbin",
657 "/usr/local/bin",
658 "/usr/sbin",
659 "/usr/bin",
660 "/sbin",
661 "/bin",
662 "/snap/bin",
663 ])
664 .build(),
665 )
666 .authentication(SAuthentication::Perform)
667 .env(
668 SEnvOptions::builder(EnvBehavior::Delete)
669 .keep([
670 "HOME",
671 "USER",
672 "LOGNAME",
673 "COLORS",
674 "DISPLAY",
675 "HOSTNAME",
676 "KRB5CCNAME",
677 "LS_COLORS",
678 "PS1",
679 "PS2",
680 "XAUTHORY",
681 "XAUTHORIZATION",
682 "XDG_CURRENT_DESKTOP",
683 ])
684 .unwrap()
685 .check([
686 "COLORTERM",
687 "LANG",
688 "LANGUAGE",
689 "LC_*",
690 "LINGUAS",
691 "TERM",
692 "TZ",
693 ])
694 .unwrap()
695 .delete([
696 "PS4",
697 "SHELLOPTS",
698 "PERLLIB",
699 "PERL5LIB",
700 "PERL5OPT",
701 "PYTHONINSPECT",
702 ])
703 .unwrap()
704 .build(),
705 )
706 .timeout(
707 STimeout::builder()
708 .type_field(TimestampType::TTY)
709 .duration(Duration::minutes(5))
710 .build(),
711 )
712 .wildcard_denied(";&|")
713 .build(),
714 ))
715 }
716}
717
718#[bon]
719impl OptStack {
720 #[builder]
721 pub fn new(
722 #[builder(field)] stack: [Option<Rc<RefCell<Opt>>>; 5],
723 roles: Option<Rc<RefCell<SConfig>>>,
724 role: Option<Rc<RefCell<SRole>>>,
725 task: Option<Rc<RefCell<STask>>>,
726 ) -> Self {
727 OptStack {
728 stack,
729 roles,
730 role,
731 task,
732 }
733 }
734 pub fn from_task(task: Rc<RefCell<STask>>) -> Self {
735 OptStack::builder().with_task(task).build()
736 }
737 pub fn from_role(role: Rc<RefCell<SRole>>) -> Self {
738 OptStack::builder().with_role(role).build()
739 }
740 pub fn from_roles(roles: Rc<RefCell<SConfig>>) -> Self {
741 OptStack::builder().with_roles(roles).build()
742 }
743
744 fn find_in_options<F: Fn(&Opt) -> Option<(Level, V)>, V>(&self, f: F) -> Option<(Level, V)> {
745 for opt in self.stack.iter().rev() {
746 if let Some(opt) = opt.to_owned() {
747 let res = f(&opt.as_ref().borrow());
748 if res.is_some() {
749 debug!("res: {:?}", res.as_ref().unwrap().0);
750 return res;
751 }
752 }
753 }
754 None
755 }
756
757 fn iter_in_options<F: FnMut(&Opt)>(&self, mut f: F) {
758 for opt in self.stack.iter() {
759 if let Some(opt) = opt.to_owned() {
760 f(&opt.as_ref().borrow());
761 }
762 }
763 }
764
765 #[cfg(feature = "finder")]
766 fn calculate_path(&self) -> String {
767 let path = self.get_final_path();
768 let default = LinkedHashSet::new();
769 println!("path: {:?}", path);
770 if let Some(add) = path.add {
771 let final_add = add.difference(path.sub.as_ref().unwrap_or(&default)).fold(
772 "".to_string(),
773 |mut acc, s| {
774 if !acc.is_empty() {
775 acc.insert(0, ':');
776 }
777 acc.insert_str(0, s);
778 acc
779 },
780 );
781 match path.default_behavior {
782 PathBehavior::Inherit | PathBehavior::Delete => final_add,
783 is_safe => std::env::vars()
784 .find_map(|(key, value)| if key == "PATH" { Some(value) } else { None })
785 .unwrap_or(String::new())
786 .split(':')
787 .filter(|s| {
788 !path.sub.as_ref().unwrap_or(&default).contains(*s)
789 && (!is_safe.is_keep_safe() || PathBuf::from(s).exists())
790 })
791 .fold(final_add, |mut acc, s| {
792 if !acc.is_empty() {
793 acc.push(':');
794 }
795 acc.push_str(s);
796 acc
797 }),
798 }
799 } else {
800 "".to_string()
801 }
802 }
803
804 fn get_final_path(&self) -> SPathOptions {
805 let mut final_behavior = PathBehavior::Delete;
806 let default = LinkedHashSet::new();
807 let final_add = rc_refcell!(LinkedHashSet::new());
808 let final_sub = rc_refcell!(LinkedHashSet::new());
810 self.iter_in_options(|opt| {
811 let final_add_clone = Rc::clone(&final_add);
812 let final_sub_clone = Rc::clone(&final_sub);
813 if let Some(p) = opt.path.borrow().as_ref() {
814 match p.default_behavior {
815 PathBehavior::KeepSafe | PathBehavior::KeepUnsafe | PathBehavior::Delete => {
816 if let Some(add) = p.add.as_ref() {
817 final_add_clone.as_ref().replace(add.clone());
818 }
819 if let Some(sub) = p.sub.as_ref() {
820 final_sub_clone.as_ref().replace(sub.clone());
821 }
822 }
823 PathBehavior::Inherit => {
824 if final_behavior.is_delete() {
825 let union: LinkedHashSet<String> = final_add_clone
826 .as_ref()
827 .borrow()
828 .union(p.add.as_ref().unwrap_or(&default))
829 .filter(|e| !p.sub.as_ref().unwrap_or(&default).contains(*e))
830 .cloned()
831 .collect();
832 final_add_clone.as_ref().borrow_mut().extend(union);
833 debug!("inherit final_add: {:?}", final_add_clone.as_ref().borrow());
834 } else {
835 let union: LinkedHashSet<String> = final_sub_clone
836 .as_ref()
837 .borrow()
838 .union(p.sub.as_ref().unwrap_or(&default))
839 .filter(|e| !p.add.as_ref().unwrap_or(&default).contains(*e))
840 .cloned()
841 .collect();
842 final_sub_clone.as_ref().borrow_mut().extend(union);
843 }
844 }
845 }
846 if !p.default_behavior.is_inherit() {
847 final_behavior = p.default_behavior;
848 }
849 }
850 });
851 SPathOptions::builder(final_behavior)
852 .add(
853 final_add
854 .clone()
855 .as_ref()
856 .borrow()
857 .iter()
858 .collect::<Vec<_>>()
859 .as_slice(),
860 )
861 .sub(
862 final_sub
863 .clone()
864 .as_ref()
865 .borrow()
866 .iter()
867 .collect::<Vec<_>>()
868 .as_slice(),
869 )
870 .build()
871 }
872
873 #[allow(dead_code)]
874 #[cfg(not(tarpaulin_include))]
875 fn union_all_path(&self) -> SPathOptions {
876 let mut final_behavior = PathBehavior::Delete;
877 let default = LinkedHashSet::new();
878 let final_add = rc_refcell!(LinkedHashSet::new());
879 let final_sub = rc_refcell!(LinkedHashSet::new());
881 self.iter_in_options(|opt| {
882 let final_add_clone = Rc::clone(&final_add);
883 let final_sub_clone = Rc::clone(&final_sub);
884 if let Some(p) = opt.path.borrow().as_ref() {
885 match p.default_behavior {
886 PathBehavior::Delete => {
887 let union = final_add_clone
888 .as_ref()
889 .borrow()
890 .union(p.add.as_ref().unwrap_or(&default))
891 .filter(|e| !p.sub.as_ref().unwrap_or(&default).contains(*e))
892 .cloned()
893 .collect();
894 final_add_clone.as_ref().replace(union);
896 debug!("delete final_add: {:?}", final_add_clone.as_ref().borrow());
897 }
898 PathBehavior::KeepSafe | PathBehavior::KeepUnsafe => {
899 let union = final_sub_clone
900 .as_ref()
901 .borrow()
902 .union(p.sub.as_ref().unwrap_or(&default))
903 .filter(|e| !p.add.as_ref().unwrap_or(&default).contains(*e))
904 .cloned()
905 .collect();
906 final_sub_clone.as_ref().replace(union);
908 }
909 PathBehavior::Inherit => {
910 if final_behavior.is_delete() {
911 let union: LinkedHashSet<String> = final_add_clone
912 .as_ref()
913 .borrow()
914 .union(p.add.as_ref().unwrap_or(&default))
915 .filter(|e| !p.sub.as_ref().unwrap_or(&default).contains(*e))
916 .cloned()
917 .collect();
918 final_add_clone.as_ref().borrow_mut().extend(union);
919 debug!("inherit final_add: {:?}", final_add_clone.as_ref().borrow());
920 } else {
921 let union: LinkedHashSet<String> = final_sub_clone
922 .as_ref()
923 .borrow()
924 .union(p.sub.as_ref().unwrap_or(&default))
925 .filter(|e| !p.add.as_ref().unwrap_or(&default).contains(*e))
926 .cloned()
927 .collect();
928 final_sub_clone.as_ref().borrow_mut().extend(union);
929 }
930 }
931 }
932 if !p.default_behavior.is_inherit() {
933 final_behavior = p.default_behavior;
934 }
935 }
936 });
937 SPathOptions::builder(final_behavior)
938 .add(
939 final_add
940 .clone()
941 .as_ref()
942 .borrow()
943 .iter()
944 .collect::<Vec<_>>()
945 .as_slice(),
946 )
947 .sub(
948 final_sub
949 .clone()
950 .as_ref()
951 .borrow()
952 .iter()
953 .collect::<Vec<_>>()
954 .as_slice(),
955 )
956 .build()
957 }
958
959 #[cfg(feature = "finder")]
960 pub fn calculate_filtered_env<I>(
961 &self,
962 opt_filter: Option<FilterMatcher>,
963 target: Cred,
964 final_env: I,
965 ) -> Result<HashMap<String, String>, String>
966 where
967 I: Iterator<Item = (String, String)>,
968 {
969 let env = self.get_final_env(opt_filter);
970 println!("env: {:?}", env);
971 if env.default_behavior.is_keep() {
972 warn!("Keeping environment variables is dangerous operation, it can lead to security vulnerabilities.
973 Please consider using delete instead.
974 See https://www.sudo.ws/security/advisories/bash_env/,
975 https://www.sudo.ws/security/advisories/perl_env/ or
976 https://nvd.nist.gov/vuln/detail/CVE-2006-0151");
977 }
978 let mut final_env: HashMap<String, String> = match env.default_behavior {
979 EnvBehavior::Inherit => Err("Internal Error with environment behavior".to_string()),
980 EnvBehavior::Delete => Ok(final_env
981 .filter_map(|(key, value)| {
982 let key = EnvKey::new(key).expect("Unexpected environment variable");
983 if env.keep.env_matches(&key)
984 || (env.check.env_matches(&key) && check_env(&key.value, &value))
985 {
986 debug!("Keeping env: {}={}", key.value, value);
987 Some((key.value, value))
988 } else {
989 debug!("Dropping env: {}", key.value);
990 None
991 }
992 })
993 .collect()),
994 EnvBehavior::Keep => Ok(final_env
995 .filter_map(|(key, value)| {
996 let key = EnvKey::new(key).expect("Unexpected environment variable");
997 if !env.delete.env_matches(&key)
998 || (env.check.env_matches(&key) && check_env(&key.value, &value))
999 {
1000 debug!("Keeping env: {}={}", key.value, value);
1001 Some((key.value, value))
1002 } else {
1003 debug!("Dropping env: {}", key.value);
1004 None
1005 }
1006 })
1007 .collect()),
1008 }?;
1009 final_env.insert("PATH".into(), self.calculate_path());
1010 final_env.insert("LOGNAME".into(), target.user.name.clone());
1011 final_env.insert("USER".into(), target.user.name);
1012 final_env.insert("HOME".into(), target.user.dir.to_string_lossy().to_string());
1013 final_env
1014 .entry("TERM".into())
1015 .or_insert_with(|| "unknown".into());
1016 final_env.insert(
1017 "SHELL".into(),
1018 target.user.shell.to_string_lossy().to_string(),
1019 );
1020 final_env.extend(env.set);
1021 Ok(final_env)
1022 }
1023
1024 fn get_final_env(&self, cmd_filter: Option<FilterMatcher>) -> SEnvOptions {
1025 let mut final_behavior = EnvBehavior::default();
1026 let mut final_set = HashMap::new();
1027 let mut final_keep = LinkedHashSet::new();
1028 let mut final_check = LinkedHashSet::new();
1029 let mut final_delete = LinkedHashSet::new();
1030 let overriden_behavior = cmd_filter.as_ref().and_then(|f| f.env_behavior);
1031 self.iter_in_options(|opt| {
1032 if let Some(p) = opt.env.borrow().as_ref() {
1033 final_behavior = match p.default_behavior {
1034 EnvBehavior::Delete | EnvBehavior::Keep => {
1035 final_keep = p
1037 .keep
1038 .as_ref()
1039 .unwrap_or(&LinkedHashSet::new())
1040 .iter()
1041 .filter(|e| {
1042 !p.set.env_matches(e)
1043 || !p.check.env_matches(e)
1044 || !p.delete.env_matches(e)
1045 })
1046 .cloned()
1047 .collect();
1048 final_check = p
1049 .check
1050 .as_ref()
1051 .unwrap_or(&LinkedHashSet::new())
1052 .iter()
1053 .filter(|e| !p.set.env_matches(e) || !p.delete.env_matches(e))
1054 .cloned()
1055 .collect();
1056 final_delete = p
1057 .delete
1058 .as_ref()
1059 .unwrap_or(&LinkedHashSet::new())
1060 .iter()
1061 .filter(|e| !p.set.env_matches(e) || !p.check.env_matches(e))
1062 .cloned()
1063 .collect();
1064 final_set = p.set.clone();
1065 debug!("check: {:?}", final_check);
1066 p.default_behavior
1067 }
1068 EnvBehavior::Inherit => {
1069 final_keep = final_keep
1070 .union(p.keep.as_ref().unwrap_or(&LinkedHashSet::new()))
1071 .cloned()
1072 .collect();
1073 final_check = final_check
1074 .union(p.check.as_ref().unwrap_or(&LinkedHashSet::new()))
1075 .cloned()
1076 .collect();
1077 final_delete = final_delete
1078 .union(p.delete.as_ref().unwrap_or(&LinkedHashSet::new()))
1079 .cloned()
1080 .collect();
1081 final_set.extend(p.set.clone());
1082 debug!("check: {:?}", final_check);
1083 final_behavior
1084 }
1085 };
1086 }
1087 });
1088 SEnvOptions::builder(overriden_behavior.unwrap_or(final_behavior))
1089 .set(final_set)
1090 .keep(final_keep)
1091 .unwrap()
1092 .check(final_check)
1093 .unwrap()
1094 .delete(final_delete)
1095 .unwrap()
1096 .build()
1097 }
1098
1099 #[allow(dead_code)]
1100 #[cfg(not(tarpaulin_include))]
1101 fn union_all_env(
1102 &self,
1103 ) -> (
1104 EnvBehavior,
1105 LinkedHashSet<EnvKey>,
1106 LinkedHashSet<EnvKey>,
1107 LinkedHashSet<EnvKey>,
1108 ) {
1109 let mut final_behavior = EnvBehavior::default();
1110 let mut final_keep = LinkedHashSet::new();
1111 let mut final_check = LinkedHashSet::new();
1112 let mut final_delete = LinkedHashSet::new();
1113 self.iter_in_options(|opt| {
1114 if let Some(p) = opt.env.borrow().as_ref() {
1115 final_behavior = match p.default_behavior {
1116 EnvBehavior::Delete => {
1117 final_keep = final_keep
1119 .union(p.keep.as_ref().unwrap_or(&LinkedHashSet::new()))
1120 .filter(|e| !p.check.env_matches(e) || !p.delete.env_matches(e))
1121 .cloned()
1122 .collect();
1123 final_check = final_check
1124 .union(p.check.as_ref().unwrap_or(&LinkedHashSet::new()))
1125 .filter(|e| !p.delete.env_matches(e))
1126 .cloned()
1127 .collect();
1128 p.default_behavior
1129 }
1130 EnvBehavior::Keep => {
1131 final_delete = final_delete
1133 .union(p.delete.as_ref().unwrap_or(&LinkedHashSet::new()))
1134 .filter(|e| !p.keep.env_matches(e) || !p.check.env_matches(e))
1135 .cloned()
1136 .collect();
1137 final_check = final_check
1138 .union(p.check.as_ref().unwrap_or(&LinkedHashSet::new()))
1139 .filter(|e| !p.keep.env_matches(e))
1140 .cloned()
1141 .collect();
1142 p.default_behavior
1143 }
1144 EnvBehavior::Inherit => {
1145 if final_behavior.is_delete() {
1146 final_keep = final_keep
1147 .union(p.keep.as_ref().unwrap_or(&LinkedHashSet::new()))
1148 .filter(|e| !p.delete.env_matches(e) || !p.check.env_matches(e))
1149 .cloned()
1150 .collect();
1151 final_check = final_check
1152 .union(p.check.as_ref().unwrap_or(&LinkedHashSet::new()))
1153 .filter(|e| !p.delete.env_matches(e))
1154 .cloned()
1155 .collect();
1156 } else {
1157 final_delete = final_delete
1158 .union(p.delete.as_ref().unwrap_or(&LinkedHashSet::new()))
1159 .filter(|e| !p.keep.env_matches(e) || !p.check.env_matches(e))
1160 .cloned()
1161 .collect();
1162 final_check = final_check
1163 .union(p.check.as_ref().unwrap_or(&LinkedHashSet::new()))
1164 .filter(|e| !p.keep.env_matches(e))
1165 .cloned()
1166 .collect();
1167 }
1168 final_behavior
1169 }
1170 };
1171 }
1172 });
1173 (final_behavior, final_keep, final_check, final_delete)
1174 }
1175 pub fn get_root_behavior(&self) -> (Level, SPrivileged) {
1176 self.find_in_options(|opt| {
1177 if let Some(p) = &opt.borrow().root {
1178 return Some((opt.level, *p));
1179 }
1180 None
1181 })
1182 .unwrap_or((Level::None, SPrivileged::default()))
1183 }
1184 pub fn get_bounding(&self) -> (Level, SBounding) {
1185 self.find_in_options(|opt| {
1186 if let Some(p) = &opt.borrow().bounding {
1187 return Some((opt.level, *p));
1188 }
1189 None
1190 })
1191 .unwrap_or((Level::None, SBounding::default()))
1192 }
1193 pub fn get_authentication(&self) -> (Level, SAuthentication) {
1194 self.find_in_options(|opt| {
1195 if let Some(p) = &opt.borrow().authentication {
1196 return Some((opt.level, *p));
1197 }
1198 None
1199 })
1200 .unwrap_or((Level::None, SAuthentication::default()))
1201 }
1202
1203 pub fn get_wildcard(&self) -> (Level, String) {
1204 self.find_in_options(|opt| {
1205 if let Some(p) = opt.borrow().wildcard_denied.borrow().as_ref() {
1206 return Some((opt.level, p.clone()));
1207 }
1208 None
1209 })
1210 .unwrap_or((Level::None, "".to_owned()))
1211 }
1212
1213 pub fn get_timeout(&self) -> (Level, STimeout) {
1214 self.find_in_options(|opt| {
1215 if let Some(p) = &opt.borrow().timeout {
1216 return Some((opt.level, p.clone()));
1217 }
1218 None
1219 })
1220 .unwrap_or((Level::None, STimeout::default()))
1221 }
1222
1223 fn get_level(&self) -> Level {
1224 let (level, _) = self
1225 .find_in_options(|opt| Some((opt.level, ())))
1226 .unwrap_or((Level::None, ()));
1227 level
1228 }
1229
1230 pub fn to_opt(&self) -> Rc<RefCell<Opt>> {
1231 Opt::builder(self.get_level())
1232 .path(self.get_final_path())
1233 .env(self.get_final_env(None))
1234 .maybe_root(
1235 self.find_in_options(|opt| opt.root.map(|root| (opt.level, root)))
1236 .map(|(_, root)| root),
1237 )
1238 .maybe_bounding(
1239 self.find_in_options(|opt| opt.bounding.map(|bounding| (opt.level, bounding)))
1240 .map(|(_, bounding)| bounding),
1241 )
1242 .maybe_authentication(
1243 self.find_in_options(|opt| {
1244 opt.authentication
1245 .map(|authentication| (opt.level, authentication))
1246 })
1247 .map(|(_, authentication)| authentication),
1248 )
1249 .maybe_wildcard_denied(
1250 self.find_in_options(|opt| {
1251 opt.wildcard_denied
1252 .borrow()
1253 .as_ref()
1254 .map(|wildcard| (opt.level, wildcard.clone()))
1255 })
1256 .map(|(_, wildcard)| wildcard),
1257 )
1258 .maybe_timeout(
1259 self.find_in_options(|opt| opt.timeout.clone().map(|timeout| (opt.level, timeout)))
1260 .map(|(_, timeout)| timeout),
1261 )
1262 .build()
1263 }
1264}
1265
1266impl PartialEq for OptStack {
1267 fn eq(&self, other: &Self) -> bool {
1268 let path = self.get_final_path();
1270 let default = LinkedHashSet::new();
1271 let other_path = other.get_final_path();
1272 let res = path.default_behavior == other_path.default_behavior
1273 && path
1274 .add
1275 .as_ref()
1276 .unwrap_or(&default)
1277 .symmetric_difference(other_path.add.as_ref().unwrap_or(&default))
1278 .count()
1279 == 0
1280 && path
1281 .sub
1282 .as_ref()
1283 .unwrap_or(&default)
1284 .symmetric_difference(other_path.sub.as_ref().unwrap_or(&default))
1285 .count()
1286 == 0
1287 && self.get_root_behavior().1 == other.get_root_behavior().1
1288 && self.get_bounding().1 == other.get_bounding().1
1289 && self.get_wildcard().1 == other.get_wildcard().1
1290 && self.get_authentication().1 == other.get_authentication().1
1291 && self.get_timeout().1 == other.get_timeout().1;
1292 debug!(
1293 "final_behavior == other_path.behavior : {}
1294 && add {:?} - other_add {:?} == 0 : {}
1295 && sub - other_sub == 0 : {}
1296 && self.get_root_behavior().1 == other.get_root_behavior().1 : {}
1297 && self.get_bounding().1 == other.get_bounding().1 : {}
1298 && self.get_wildcard().1 == other.get_wildcard().1 : {}
1299 && self.get_authentication().1 == other.get_authentication().1 : {}
1300 && self.get_timeout().1 == other.get_timeout().1 : {}",
1301 path.default_behavior == other_path.default_behavior,
1302 path.add,
1303 other_path.add,
1304 path.add
1305 .as_ref()
1306 .unwrap_or(&default)
1307 .symmetric_difference(other_path.add.as_ref().unwrap_or(&default))
1308 .count()
1309 == 0,
1310 path.sub
1311 .as_ref()
1312 .unwrap_or(&default)
1313 .symmetric_difference(other_path.sub.as_ref().unwrap_or(&default))
1314 .count()
1315 == 0,
1316 self.get_root_behavior().1 == other.get_root_behavior().1,
1317 self.get_bounding().1 == other.get_bounding().1,
1318 self.get_wildcard().1 == other.get_wildcard().1,
1319 self.get_authentication().1 == other.get_authentication().1,
1320 self.get_timeout().1 == other.get_timeout().1
1321 );
1322 debug!("OPT check: {}", res);
1323 res
1324 }
1325}
1326
1327#[cfg(test)]
1328mod tests {
1329
1330 use nix::unistd::Pid;
1331
1332 use super::super::options::*;
1333 use super::super::structs::*;
1334
1335 fn env_key_set_equal<I, J>(a: I, b: J) -> bool
1336 where
1337 I: IntoIterator<Item = EnvKey>,
1338 J: IntoIterator<Item = EnvKey>,
1339 {
1340 let mut a_vec: Vec<_> = a.into_iter().collect();
1341 let mut b_vec: Vec<_> = b.into_iter().collect();
1342 a_vec.sort_by(|a, b| a.value.cmp(&b.value));
1343 b_vec.sort_by(|a, b| a.value.cmp(&b.value));
1344 a_vec == b_vec
1345 }
1346
1347 fn hashset_vec_equal<I, J>(a: I, b: J) -> bool
1348 where
1349 I: IntoIterator,
1350 I::Item: Into<String>,
1351 J: IntoIterator,
1352 J::Item: Into<String>,
1353 {
1354 let mut a_vec: Vec<String> = a.into_iter().map(Into::into).collect();
1355 let mut b_vec: Vec<String> = b.into_iter().map(Into::into).collect();
1356 a_vec.sort();
1357 b_vec.sort();
1358 a_vec == b_vec
1359 }
1360
1361 #[test]
1362 fn test_find_in_options() {
1363 let config = SConfig::builder()
1364 .role(
1365 SRole::builder("test")
1366 .options(|opt| {
1367 opt.path(
1368 SPathOptions::builder(PathBehavior::Inherit)
1369 .add(["path2"])
1370 .build(),
1371 )
1372 .build()
1373 })
1374 .build(),
1375 )
1376 .options(|opt| {
1377 opt.path(
1378 SPathOptions::builder(PathBehavior::Delete)
1379 .add(["path1"])
1380 .build(),
1381 )
1382 .build()
1383 })
1384 .build();
1385 let options = OptStack::from_role(config.as_ref().borrow().roles[0].clone());
1386 let res: Option<(Level, SPathOptions)> =
1387 options.find_in_options(|opt| opt.path.clone().map(|value| (opt.level, value)));
1388 assert_eq!(
1389 res,
1390 Some((
1391 Level::Role,
1392 SPathOptions::builder(PathBehavior::Inherit)
1393 .add(["path2"])
1394 .build()
1395 ))
1396 );
1397 }
1398
1399 #[cfg(feature = "finder")]
1400 #[test]
1401 fn test_get_path() {
1402 let config = SConfig::builder()
1403 .role(
1404 SRole::builder("test")
1405 .options(|opt| {
1406 opt.path(
1407 SPathOptions::builder(PathBehavior::Inherit)
1408 .add(["path2"])
1409 .build(),
1410 )
1411 .build()
1412 })
1413 .build(),
1414 )
1415 .options(|opt| {
1416 opt.path(
1417 SPathOptions::builder(PathBehavior::Delete)
1418 .add(["path1"])
1419 .build(),
1420 )
1421 .build()
1422 })
1423 .build();
1424 let options = OptStack::from_role(config.as_ref().borrow().roles.first().unwrap().clone());
1425 let res = options.calculate_path();
1426 assert_eq!(res, "path2:path1");
1427 }
1428
1429 #[cfg(feature = "finder")]
1430 #[test]
1431 fn test_get_path_delete() {
1432 let config = SConfig::builder()
1433 .role(
1434 SRole::builder("test")
1435 .options(|opt| {
1436 opt.path(
1437 SPathOptions::builder(PathBehavior::Delete)
1438 .add(["path2"])
1439 .build(),
1440 )
1441 .build()
1442 })
1443 .build(),
1444 )
1445 .options(|opt| {
1446 opt.path(
1447 SPathOptions::builder(PathBehavior::Delete)
1448 .add(["path1"])
1449 .build(),
1450 )
1451 .build()
1452 })
1453 .build();
1454 let options = OptStack::from_role(config.role("test").unwrap()).calculate_path();
1455 assert!(options.contains("path2"));
1456 }
1457
1458 #[cfg(feature = "finder")]
1459 #[test]
1460 fn test_opt_add_sub() {
1461 let config = SConfig::builder()
1462 .role(
1463 SRole::builder("test")
1464 .options(|opt| {
1465 opt.path(
1466 SPathOptions::builder(PathBehavior::Delete)
1467 .sub(["path1"])
1468 .build(),
1469 )
1470 .build()
1471 })
1472 .build(),
1473 )
1474 .options(|opt| {
1475 opt.path(
1476 SPathOptions::builder(PathBehavior::Delete)
1477 .add(["path1"])
1478 .build(),
1479 )
1480 .build()
1481 })
1482 .build();
1483 let options = OptStack::from_role(config.role("test").unwrap()).calculate_path();
1484 assert!(!options.contains("path1"));
1485 }
1486
1487 #[test]
1488 fn test_env_global_to_task() {
1489 let config = SConfig::builder()
1490 .role(
1491 SRole::builder("test")
1492 .task(
1493 STask::builder(1)
1494 .options(|opt| {
1495 opt.env(
1496 SEnvOptions::builder(EnvBehavior::Delete)
1497 .keep(["env1"])
1498 .unwrap()
1499 .build(),
1500 )
1501 .build()
1502 })
1503 .build(),
1504 )
1505 .options(|opt| {
1506 opt.env(
1507 SEnvOptions::builder(EnvBehavior::Delete)
1508 .keep(["env2"])
1509 .unwrap()
1510 .build(),
1511 )
1512 .build()
1513 })
1514 .build(),
1515 )
1516 .options(|opt| {
1517 opt.env(
1518 SEnvOptions::builder(EnvBehavior::Delete)
1519 .keep(["env3"])
1520 .unwrap()
1521 .build(),
1522 )
1523 .build()
1524 })
1525 .build();
1526 let binding = OptStack::from_task(config.task("test", 1).unwrap()).to_opt();
1527 let options = binding.as_ref().borrow();
1528 let res = &options.env.as_ref().unwrap().keep;
1529 assert!(res
1530 .as_ref()
1531 .unwrap_or(&LinkedHashSet::new())
1532 .contains(&EnvKey::from("env1")));
1533 }
1534
1535 #[test]
1537 fn test_to_opt() {
1538 let config = SConfig::builder()
1539 .role(
1540 SRole::builder("test")
1541 .task(
1542 STask::builder(1)
1543 .options(|opt| {
1544 opt.path(
1545 SPathOptions::builder(PathBehavior::Inherit)
1546 .add(["path3"])
1547 .build(),
1548 )
1549 .env(
1550 SEnvOptions::builder(EnvBehavior::Inherit)
1551 .keep(["env3"])
1552 .unwrap()
1553 .build(),
1554 )
1555 .root(SPrivileged::User)
1556 .bounding(SBounding::Strict)
1557 .authentication(SAuthentication::Perform)
1558 .timeout(
1559 STimeout::builder()
1560 .type_field(TimestampType::TTY)
1561 .duration(Duration::minutes(3))
1562 .build(),
1563 )
1564 .wildcard_denied("c")
1565 .build()
1566 })
1567 .build(),
1568 )
1569 .options(|opt| {
1570 opt.path(
1571 SPathOptions::builder(PathBehavior::Inherit)
1572 .add(["path2"])
1573 .build(),
1574 )
1575 .env(
1576 SEnvOptions::builder(EnvBehavior::Delete)
1577 .keep(["env1"])
1578 .unwrap()
1579 .build(),
1580 )
1581 .root(SPrivileged::Privileged)
1582 .bounding(SBounding::Strict)
1583 .authentication(SAuthentication::Skip)
1584 .timeout(
1585 STimeout::builder()
1586 .type_field(TimestampType::PPID)
1587 .duration(Duration::minutes(2))
1588 .build(),
1589 )
1590 .wildcard_denied("b")
1591 .build()
1592 })
1593 .build(),
1594 )
1595 .options(|opt| {
1596 opt.path(
1597 SPathOptions::builder(PathBehavior::Delete)
1598 .add(["path1"])
1599 .build(),
1600 )
1601 .env(
1602 SEnvOptions::builder(EnvBehavior::Delete)
1603 .keep(["env2"])
1604 .unwrap()
1605 .build(),
1606 )
1607 .root(SPrivileged::Privileged)
1608 .bounding(SBounding::Ignore)
1609 .authentication(SAuthentication::Perform)
1610 .timeout(
1611 STimeout::builder()
1612 .type_field(TimestampType::TTY)
1613 .duration(Duration::minutes(1))
1614 .build(),
1615 )
1616 .wildcard_denied("a")
1617 .build()
1618 })
1619 .build();
1620 let default = LinkedHashSet::new();
1621 let stack = OptStack::from_roles(config.clone());
1622 let opt = stack.to_opt();
1623 let global_options = opt.as_ref().borrow();
1624 assert_eq!(
1625 global_options.path.as_ref().unwrap().default_behavior,
1626 PathBehavior::Delete
1627 );
1628 assert!(hashset_vec_equal(
1629 global_options
1630 .path
1631 .as_ref()
1632 .unwrap()
1633 .add
1634 .as_ref()
1635 .unwrap_or(&default)
1636 .clone(),
1637 vec!["path1"]
1638 ));
1639 assert_eq!(
1640 global_options.env.as_ref().unwrap().default_behavior,
1641 EnvBehavior::Delete
1642 );
1643 assert!(env_key_set_equal(
1644 global_options
1645 .env
1646 .as_ref()
1647 .unwrap()
1648 .keep
1649 .as_ref()
1650 .unwrap_or(&LinkedHashSet::new())
1651 .clone(),
1652 vec![EnvKey::from("env2")]
1653 ));
1654 assert_eq!(global_options.root.unwrap(), SPrivileged::Privileged);
1655 assert_eq!(global_options.bounding.unwrap(), SBounding::Ignore);
1656 assert_eq!(
1657 global_options.authentication.unwrap(),
1658 SAuthentication::Perform
1659 );
1660 assert_eq!(
1661 global_options.timeout.as_ref().unwrap().duration.unwrap(),
1662 Duration::minutes(1)
1663 );
1664 assert_eq!(
1665 global_options.timeout.as_ref().unwrap().type_field.unwrap(),
1666 TimestampType::TTY
1667 );
1668 assert_eq!(global_options.wildcard_denied.as_ref().unwrap(), "a");
1669 let opt = OptStack::from_role(config.clone().role("test").unwrap()).to_opt();
1670 let role_options = opt.as_ref().borrow();
1671 assert_eq!(
1672 role_options.path.as_ref().unwrap().default_behavior,
1673 PathBehavior::Delete
1674 );
1675 assert!(hashset_vec_equal(
1676 role_options
1677 .path
1678 .as_ref()
1679 .unwrap()
1680 .add
1681 .as_ref()
1682 .unwrap_or(&default)
1683 .clone(),
1684 vec!["path1", "path2"]
1685 ));
1686 assert_eq!(
1687 role_options.env.as_ref().unwrap().default_behavior,
1688 EnvBehavior::Delete
1689 );
1690 assert!(env_key_set_equal(
1691 role_options
1692 .env
1693 .as_ref()
1694 .unwrap()
1695 .keep
1696 .as_ref()
1697 .unwrap_or(&LinkedHashSet::new())
1698 .clone(),
1699 vec![EnvKey::from("env1")]
1700 ));
1701 assert_eq!(role_options.root.unwrap(), SPrivileged::Privileged);
1702 assert_eq!(role_options.bounding.unwrap(), SBounding::Strict);
1703 assert_eq!(role_options.authentication.unwrap(), SAuthentication::Skip);
1704 assert_eq!(
1705 role_options.timeout.as_ref().unwrap().duration.unwrap(),
1706 Duration::minutes(2)
1707 );
1708 assert_eq!(
1709 role_options.timeout.as_ref().unwrap().type_field.unwrap(),
1710 TimestampType::PPID
1711 );
1712 assert_eq!(role_options.wildcard_denied.as_ref().unwrap(), "b");
1713 let opt = OptStack::from_task(config.task("test", 1).unwrap()).to_opt();
1714 let task_options = opt.as_ref().borrow();
1715 assert_eq!(
1716 task_options.path.as_ref().unwrap().default_behavior,
1717 PathBehavior::Delete
1718 );
1719 assert!(hashset_vec_equal(
1720 task_options
1721 .path
1722 .as_ref()
1723 .unwrap()
1724 .add
1725 .as_ref()
1726 .unwrap_or(&default)
1727 .clone(),
1728 vec!["path1", "path2", "path3"]
1729 ));
1730 assert_eq!(
1731 task_options.env.as_ref().unwrap().default_behavior,
1732 EnvBehavior::Delete
1733 );
1734 assert!(env_key_set_equal(
1735 task_options
1736 .env
1737 .as_ref()
1738 .unwrap()
1739 .keep
1740 .as_ref()
1741 .unwrap_or(&LinkedHashSet::new())
1742 .clone(),
1743 vec![EnvKey::from("env1"), EnvKey::from("env3")]
1744 ));
1745 assert_eq!(task_options.root.unwrap(), SPrivileged::User);
1746 assert_eq!(task_options.bounding.unwrap(), SBounding::Strict);
1747 assert_eq!(
1748 task_options.authentication.unwrap(),
1749 SAuthentication::Perform
1750 );
1751 assert_eq!(
1752 task_options.timeout.as_ref().unwrap().duration.unwrap(),
1753 Duration::minutes(3)
1754 );
1755 assert_eq!(
1756 task_options.timeout.as_ref().unwrap().type_field.unwrap(),
1757 TimestampType::TTY
1758 );
1759 assert_eq!(task_options.wildcard_denied.as_ref().unwrap(), "c");
1760 }
1761
1762 #[test]
1763 fn test_get_timeout() {
1764 let config = SConfig::builder()
1765 .role(
1766 SRole::builder("test")
1767 .options(|opt| {
1768 opt.timeout(STimeout::builder().duration(Duration::minutes(5)).build())
1769 .build()
1770 })
1771 .build(),
1772 )
1773 .options(|opt| {
1774 opt.timeout(
1775 STimeout::builder()
1776 .type_field(TimestampType::PPID)
1777 .duration(Duration::minutes(10))
1778 .build(),
1779 )
1780 .build()
1781 })
1782 .build();
1783 let options = OptStack::from_role(config.role("test").unwrap()).get_timeout();
1784 assert_eq!(options.1.duration.unwrap(), Duration::minutes(5));
1785 assert_eq!(options.0, Level::Role);
1786 assert!(options.1.type_field.is_none());
1787 }
1788
1789 #[test]
1790 fn test_get_root_behavior() {
1791 let config = SConfig::builder()
1792 .role(
1793 SRole::builder("test")
1794 .task(STask::builder(1).build())
1795 .options(|opt| opt.root(SPrivileged::User).build())
1796 .build(),
1797 )
1798 .options(|opt| opt.root(SPrivileged::Privileged).build())
1799 .build();
1800 let (level, sprivilege) =
1801 OptStack::from_task(config.task("test", 1).unwrap()).get_root_behavior();
1802 assert_eq!(level, Level::Role);
1803 assert_eq!(sprivilege, SPrivileged::User);
1804 }
1805
1806 #[test]
1807 fn test_get_bounding() {
1808 let config = SConfig::builder()
1809 .role(
1810 SRole::builder("test")
1811 .options(|opt| opt.bounding(SBounding::Strict).build())
1812 .build(),
1813 )
1814 .options(|opt| opt.bounding(SBounding::Ignore).build())
1815 .build();
1816 let (level, bounding) = OptStack::from_role(config.role("test").unwrap()).get_bounding();
1817 assert_eq!(level, Level::Role);
1818 assert_eq!(bounding, SBounding::Strict);
1819 }
1820
1821 #[test]
1822 fn test_get_wildcard() {
1823 let config = SConfig::builder()
1824 .role(
1825 SRole::builder("test")
1826 .options(|opt| opt.wildcard_denied("b").build())
1827 .build(),
1828 )
1829 .options(|opt| opt.wildcard_denied("a").build())
1830 .build();
1831 let (level, wildcard) = OptStack::from_role(config.role("test").unwrap()).get_wildcard();
1832 assert_eq!(level, Level::Role);
1833 assert_eq!(wildcard, "b");
1834 }
1835
1836 #[cfg(feature = "finder")]
1837 #[test]
1838 fn test_tz_is_safe() {
1839 assert!(tz_is_safe("America/New_York"));
1840 assert!(!tz_is_safe("/America/New_York"));
1841 assert!(!tz_is_safe("America/New_York/.."));
1842 assert!(!tz_is_safe(
1844 String::from_utf8(vec![b'a'; (PATH_MAX + 1).try_into().unwrap()])
1845 .unwrap()
1846 .as_str()
1847 ));
1848 }
1849
1850 #[cfg(feature = "finder")]
1851 #[test]
1852 fn test_check_env() {
1853 let config = SConfig::builder()
1854 .role(
1855 SRole::builder("test")
1856 .options(|opt| {
1857 opt.env(
1858 SEnvOptions::builder(EnvBehavior::Inherit)
1859 .check(["env2"])
1860 .unwrap()
1861 .build(),
1862 )
1863 .build()
1864 })
1865 .task(
1866 STask::builder(IdTask::Number(1))
1867 .options(|opt| {
1868 opt.env(
1869 SEnvOptions::builder(EnvBehavior::Inherit)
1870 .keep(["env1"])
1871 .unwrap()
1872 .build(),
1873 )
1874 .build()
1875 })
1876 .build(),
1877 )
1878 .build(),
1879 )
1880 .options(|opt| {
1881 opt.env(
1882 SEnvOptions::builder(EnvBehavior::Delete)
1883 .check(["env3"])
1884 .unwrap()
1885 .set([("env4".to_string(), "value4".to_string())])
1886 .build(),
1887 )
1888 .build()
1889 })
1890 .build();
1891 let options = OptStack::from_task(config.task("test", 1).unwrap());
1892 let mut test_env = HashMap::new();
1893 test_env.insert("env1".to_string(), "value1".to_string());
1894 test_env.insert("env2".into(), "va%lue2".into());
1895 test_env.insert("env3".into(), "value3".into());
1896 let cred = Cred::builder()
1897 .user_id(0)
1898 .group_id(0)
1899 .ppid(Pid::from_raw(0))
1900 .build();
1901 let result = options
1902 .calculate_filtered_env(None, cred, test_env.into_iter())
1903 .unwrap();
1904 assert_eq!(result.get("env1").unwrap(), "value1");
1905 assert_eq!(result.get("env3").unwrap(), "value3");
1906 assert!(result.get("env2").is_none());
1907 assert_eq!(result.get("env4").unwrap(), "value4");
1908 }
1909
1910 #[cfg(feature = "finder")]
1911 #[test]
1912 fn test_override_env() {
1913 let config = SConfig::builder()
1914 .role(
1915 SRole::builder("test")
1916 .task(
1917 STask::builder(IdTask::Number(1))
1918 .options(|opt| {
1919 opt.env(
1920 SEnvOptions::builder(EnvBehavior::Inherit)
1921 .keep(["env1"])
1922 .unwrap()
1923 .build(),
1924 )
1925 .build()
1926 })
1927 .build(),
1928 )
1929 .options(|opt| {
1930 opt.env(
1931 SEnvOptions::builder(EnvBehavior::Inherit)
1932 .check(["env2"])
1933 .unwrap()
1934 .build(),
1935 )
1936 .build()
1937 })
1938 .build(),
1939 )
1940 .options(|opt| {
1941 opt.env(
1942 SEnvOptions::builder(EnvBehavior::Delete)
1943 .check(["env3"])
1944 .unwrap()
1945 .set([("env4".to_string(), "value4".to_string())])
1946 .build(),
1947 )
1948 .build()
1949 })
1950 .build();
1951
1952 let options = OptStack::from_task(config.task("test", 1).unwrap());
1953 let mut test_env = HashMap::new();
1954 test_env.insert("env1".to_string(), "value1".to_string());
1955 test_env.insert("env2".into(), "va%lue2".into());
1956 test_env.insert("env3".into(), "value3".into());
1957 let cred = Cred::builder().user_id(0).group_id(0).build();
1958 let result = options
1959 .calculate_filtered_env(None, cred, test_env.into_iter())
1960 .unwrap();
1961 assert_eq!(result.get("env1").unwrap(), "value1");
1962 assert_eq!(result.get("env3").unwrap(), "value3");
1963 assert!(result.get("env2").is_none());
1964 assert_eq!(result.get("env4").unwrap(), "value4");
1965 }
1966
1967 #[test]
1968 fn is_wildcard_env_key() {
1969 assert!(!is_valid_env_name("TEST_.*"));
1970 assert!(!is_valid_env_name("123"));
1971 assert!(!is_valid_env_name(""));
1972 assert!(is_regex("TEST_.*"));
1973 }
1974
1975 #[test]
1976 fn test_wildcard_env() {
1977 let config = SConfig::builder()
1978 .role(
1979 SRole::builder("test")
1980 .task(
1981 STask::builder(IdTask::Number(1))
1982 .options(|opt| {
1983 opt.env(
1984 SEnvOptions::builder(EnvBehavior::Delete)
1985 .keep(["TEST_.*"])
1986 .unwrap()
1987 .build(),
1988 )
1989 .build()
1990 })
1991 .build(),
1992 )
1993 .build(),
1994 )
1995 .build();
1996 let options = OptStack::from_task(config.task("test", 1).unwrap());
1997 let mut test_env = HashMap::new();
1998 test_env.insert("TEST_A".to_string(), "value1".to_string());
1999 test_env.insert("TEST_B".into(), "value2".into());
2000 test_env.insert("TESTaA".into(), "value3".into());
2001 let cred = Cred::builder().user_id(0).group_id(0).build();
2002 let result = options
2003 .calculate_filtered_env(None, cred, test_env.into_iter())
2004 .unwrap();
2005 assert_eq!(result.get("TEST_A").unwrap(), "value1");
2006 assert_eq!(result.get("TEST_B").unwrap(), "value2");
2007 assert!(result.get("TESTaA").is_none());
2008 }
2009
2010 #[test]
2011 fn test_safe_path() {
2012 let path = std::env::var("PATH").unwrap();
2013 std::env::set_var("PATH", "/sys:./proc:/tmp:/bin");
2014 let config = SConfig::builder()
2015 .role(
2016 SRole::builder("test")
2017 .task(
2018 STask::builder(IdTask::Number(1))
2019 .options(|opt| {
2020 opt.path(
2021 SPathOptions::builder(PathBehavior::KeepSafe)
2022 .add(Vec::<String>::new())
2023 .sub(["/sys"])
2024 .build(),
2025 )
2026 .build()
2027 })
2028 .build(),
2029 )
2030 .build(),
2031 )
2032 .build();
2033 let options = OptStack::from_task(config.task("test", 1).unwrap());
2034 let res = options.calculate_path();
2035
2036 assert_eq!(res, "/tmp:/bin");
2037 std::env::set_var("PATH", path);
2038 }
2039
2040 #[test]
2041 fn test_unsafe_path() {
2042 let path = std::env::var("PATH").unwrap();
2043
2044 let config = SConfig::builder()
2045 .role(
2046 SRole::builder("test")
2047 .task(
2048 STask::builder(IdTask::Number(1))
2049 .options(|opt| {
2050 opt.path(
2051 SPathOptions::builder(PathBehavior::KeepUnsafe)
2052 .add(Vec::<String>::new())
2053 .sub(["/sys"])
2054 .build(),
2055 )
2056 .build()
2057 })
2058 .build(),
2059 )
2060 .build(),
2061 )
2062 .build();
2063 let options = OptStack::from_task(config.task("test", 1).unwrap());
2064 std::env::set_var("PATH", "/sys:./proc:/tmp:/bin");
2065 let res = options.calculate_path();
2066 assert_eq!(res, "./proc:/tmp:/bin");
2067 std::env::set_var("PATH", path);
2068 }
2069
2070 #[test]
2071 fn test_inherit_keep_path() {
2072 let path = std::env::var("PATH").unwrap();
2073 let config = SConfig::builder()
2074 .role(
2075 SRole::builder("test")
2076 .task(
2077 STask::builder(IdTask::Number(1))
2078 .options(|opt| {
2079 opt.path(
2080 SPathOptions::builder(PathBehavior::Inherit)
2081 .add(Vec::<String>::new())
2082 .sub(["/sys"])
2083 .build(),
2084 )
2085 .build()
2086 })
2087 .build(),
2088 )
2089 .options(|opt| {
2090 opt.path(
2091 SPathOptions::builder(PathBehavior::KeepSafe)
2092 .add(Vec::<String>::new())
2093 .sub(["/tmp"])
2094 .build(),
2095 )
2096 .build()
2097 })
2098 .build(),
2099 )
2100 .build();
2101 let options = OptStack::from_task(config.task("test", 1).unwrap());
2102 std::env::set_var("PATH", "/sys:./proc:/tmp:/bin");
2103 let res = options.calculate_path();
2104
2105 assert_eq!(res, "/bin");
2106 std::env::set_var("PATH", path);
2107 }
2108
2109 #[test]
2110 fn test_final_env_keep() {
2111 let config = SConfig::builder()
2112 .role(
2113 SRole::builder("test")
2114 .task(
2115 STask::builder(IdTask::Number(1))
2116 .options(|opt| {
2117 opt.env(
2118 SEnvOptions::builder(EnvBehavior::Inherit)
2119 .delete(["env1"])
2120 .unwrap()
2121 .build(),
2122 )
2123 .build()
2124 })
2125 .build(),
2126 )
2127 .options(|opt| {
2128 opt.env(
2129 SEnvOptions::builder(EnvBehavior::Inherit)
2130 .delete(["env2"])
2131 .unwrap()
2132 .build(),
2133 )
2134 .build()
2135 })
2136 .build(),
2137 )
2138 .options(|opt| {
2139 opt.env(
2140 SEnvOptions::builder(EnvBehavior::Keep)
2141 .delete(["env3"])
2142 .unwrap()
2143 .build(),
2144 )
2145 .build()
2146 })
2147 .build();
2148 let options = OptStack::from_task(config.task("test", 1).unwrap());
2149 let test_env = [
2150 ("env1", "value1"),
2151 ("env2", "value2"),
2152 ("env3", "value3"),
2153 ("env4", "value4"),
2154 ("env5", "value5"),
2155 ]
2156 .iter()
2157 .map(|(k, v)| (k.to_string(), v.to_string()));
2158
2159 let cred = Cred::builder().user_id(0).group_id(0).build();
2160 let result = options
2161 .calculate_filtered_env(None, cred, test_env.into_iter())
2162 .unwrap();
2163 assert!(result.get("env1").is_none());
2164 assert!(result.get("env2").is_none());
2165 assert!(result.get("env3").is_none());
2166 assert_eq!(result.get("env4").unwrap(), "value4");
2167 assert_eq!(result.get("env5").unwrap(), "value5");
2168 }
2169
2170 #[test]
2171 fn test_opt_filter_env() {
2172 let config = SConfig::builder()
2173 .role(
2174 SRole::builder("test")
2175 .task(
2176 STask::builder(IdTask::Number(1))
2177 .options(|opt| {
2178 opt.env(
2179 SEnvOptions::builder(EnvBehavior::Delete)
2180 .delete(["envA"])
2181 .unwrap()
2182 .override_behavior(true)
2183 .build(),
2184 )
2185 .build()
2186 })
2187 .build(),
2188 )
2189 .build(),
2190 )
2191 .build();
2192 let options = OptStack::from_task(config.task("test", 1).unwrap());
2193 let test_env = [("envA", "value1"), ("envB", "value2"), ("envC", "value3")]
2194 .iter()
2195 .map(|(k, v)| (k.to_string(), v.to_string()));
2196
2197 let cred = Cred::builder().user_id(0).group_id(0).build();
2198 let result = options
2199 .calculate_filtered_env(
2200 Some(
2201 FilterMatcher::builder()
2202 .env_behavior(EnvBehavior::Keep)
2203 .build(),
2204 ),
2205 cred,
2206 test_env.into_iter(),
2207 )
2208 .unwrap();
2209 assert!(result.get("envA").is_none());
2210 assert_eq!(result.get("envB").unwrap(), "value2");
2211 assert_eq!(result.get("envC").unwrap(), "value3");
2212 }
2213}