1
//! Functionality for disabling `fs-mistrust` checks based on configuration or
2
//! environment variables.
3

            
4
use std::env::{self, VarError};
5

            
6
/// Convenience type to indicate whether permission checks are disabled.
7
///
8
/// Used to avoid accidents with boolean meanings.
9
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
10
pub(crate) enum Status {
11
    /// We should indeed run permission checks, and treat some users as untrusted.
12
    CheckPermissions,
13
    /// We should treat every user as trusted, and therefore disable (most)
14
    /// permissions checks.
15
    DisableChecks,
16
}
17

            
18
impl Status {
19
    /// Return true if this `Status` tells us to disable checks.
20
184240
    pub(crate) fn disabled(self) -> bool {
21
184240
        self == Status::DisableChecks
22
184240
    }
23
}
24

            
25
/// An environment variable which, if set, will cause a us to trust all users
26
/// (and therefore, in effect, to disable all permissions checks.)
27
pub const GLOBAL_DISABLE_VAR: &str = "FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS";
28

            
29
/// Value to configure when permission checks should be disabled.  This type is
30
/// set in the builder, and converted to a bool in the `Mistrust`.
31
#[derive(Clone, Default, Debug, Eq, PartialEq)]
32
pub(crate) enum Disable {
33
    /// Check a caller-provided environment variable, and honor it if it is set.
34
    /// If it is not set, fall back to checking
35
    /// `$FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS`.
36
    OnUserEnvVar(String),
37
    /// Disable permissions checks if the value of
38
    /// `$FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS` is something other than "false",
39
    /// "0", "no", etc..
40
    ///
41
    /// This is the default.
42
    #[default]
43
    OnGlobalEnvVar,
44
    /// Perform permissions checks regardless of any values in the environment.
45
    Never,
46
}
47

            
48
/// Convert the result of `std::env::var` to a boolean, if the variable is set.
49
///
50
/// Names that seem to say "don't disable" are treated as `Some(false)`.  Any
51
/// other value is treated as `Some(true)`.  (That is, we err on the side of
52
/// assuming that if you set a disable variable, you meant to disable.)
53
///
54
/// Absent environment vars, or those set to the empty string, are treated as
55
/// None.
56
#[allow(clippy::match_like_matches_macro)]
57
18398
fn from_env_var_value(input: std::result::Result<String, VarError>) -> Option<Status> {
58
    // WARNING: This behaviour of the environment variable parsing/evaluation is considered
59
    // stable and should not be modified unless necessary.
60
    // This behaviour is part of the public interface of applications which use fs-mistrust,
61
    // so changing the behaviour of this function may result in a breaking change for applications.
62

            
63
18370
    let mut s = match input {
64
28
        Ok(s) => s,
65
18368
        Err(VarError::NotPresent) => return None,
66
2
        Err(VarError::NotUnicode(_)) => return Some(Status::DisableChecks),
67
    };
68

            
69
28
    s.make_ascii_lowercase();
70
28
    let s = s.trim();
71
28

            
72
28
    match s {
73
28
        "" => None,
74
24
        "0" | "no" | "never" | "false" | "n" => Some(Status::CheckPermissions),
75
10
        _ => Some(Status::DisableChecks),
76
    }
77
18398
}
78

            
79
/// As `from_env_value`, but takes the name of the variable.
80
18366
fn from_env_var(varname: &str) -> Option<Status> {
81
18366
    from_env_var_value(env::var(varname))
82
18366
}
83

            
84
impl Disable {
85
    /// Return true if, based on this [`Disable`] setting, and on the
86
    /// environment, we should disable permissions checking.
87
11888
    pub(crate) fn should_disable_checks(&self) -> Status {
88
11888
        match self {
89
6478
            Disable::OnUserEnvVar(varname) => from_env_var(varname)
90
6560
                .or_else(|| from_env_var(GLOBAL_DISABLE_VAR))
91
6478
                .unwrap_or(Status::CheckPermissions),
92
            Disable::OnGlobalEnvVar => {
93
5410
                from_env_var(GLOBAL_DISABLE_VAR).unwrap_or(Status::CheckPermissions)
94
            }
95
            Disable::Never => Status::CheckPermissions,
96
        }
97
11888
    }
98
}
99

            
100
#[cfg(test)]
101
mod test {
102
    // @@ begin test lint list maintained by maint/add_warning @@
103
    #![allow(clippy::bool_assert_comparison)]
104
    #![allow(clippy::clone_on_copy)]
105
    #![allow(clippy::dbg_macro)]
106
    #![allow(clippy::mixed_attributes_style)]
107
    #![allow(clippy::print_stderr)]
108
    #![allow(clippy::print_stdout)]
109
    #![allow(clippy::single_char_pattern)]
110
    #![allow(clippy::unwrap_used)]
111
    #![allow(clippy::unchecked_duration_subtraction)]
112
    #![allow(clippy::useless_vec)]
113
    #![allow(clippy::needless_pass_by_value)]
114
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
115
    use super::*;
116
    #[test]
117
    fn from_val() {
118
        for word in ["yes", "1", "true", "certainly", "whatever"] {
119
            assert_eq!(
120
                from_env_var_value(Ok(word.into())),
121
                Some(Status::DisableChecks)
122
            );
123
        }
124

            
125
        for word in ["no", "0", "false", "NO", "Never", "n"] {
126
            assert_eq!(
127
                from_env_var_value(Ok(word.into())),
128
                Some(Status::CheckPermissions)
129
            );
130
        }
131

            
132
        assert_eq!(from_env_var_value(Ok("".into())), None);
133
        assert_eq!(from_env_var_value(Ok(" ".into())), None);
134

            
135
        assert_eq!(from_env_var_value(Err(VarError::NotPresent)), None);
136
        assert_eq!(
137
            from_env_var_value(Err(VarError::NotUnicode("".into()))),
138
            Some(Status::DisableChecks)
139
        );
140

            
141
        // see https://gitlab.torproject.org/tpo/core/arti/-/issues/1782
142
        assert_eq!(
143
            from_env_var_value(Ok(" false ".to_string())),
144
            Some(Status::CheckPermissions),
145
        );
146
    }
147
}