fs_mistrust/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2#![doc = include_str!("../README.md")]
3// TODO: Stuff to add before this crate is ready....
4//  - Test the absolute heck out of it.
5
6// POSSIBLY TODO:
7//  - Cache information across runs.
8
9// @@ begin lint list maintained by maint/add_warning @@
10#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
11#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
12#![warn(missing_docs)]
13#![warn(noop_method_call)]
14#![warn(unreachable_pub)]
15#![warn(clippy::all)]
16#![deny(clippy::await_holding_lock)]
17#![deny(clippy::cargo_common_metadata)]
18#![deny(clippy::cast_lossless)]
19#![deny(clippy::checked_conversions)]
20#![warn(clippy::cognitive_complexity)]
21#![deny(clippy::debug_assert_with_mut_call)]
22#![deny(clippy::exhaustive_enums)]
23#![deny(clippy::exhaustive_structs)]
24#![deny(clippy::expl_impl_clone_on_copy)]
25#![deny(clippy::fallible_impl_from)]
26#![deny(clippy::implicit_clone)]
27#![deny(clippy::large_stack_arrays)]
28#![warn(clippy::manual_ok_or)]
29#![deny(clippy::missing_docs_in_private_items)]
30#![warn(clippy::needless_borrow)]
31#![warn(clippy::needless_pass_by_value)]
32#![warn(clippy::option_option)]
33#![deny(clippy::print_stderr)]
34#![deny(clippy::print_stdout)]
35#![warn(clippy::rc_buffer)]
36#![deny(clippy::ref_option_ref)]
37#![warn(clippy::semicolon_if_nothing_returned)]
38#![warn(clippy::trait_duplication_in_bounds)]
39#![deny(clippy::unchecked_duration_subtraction)]
40#![deny(clippy::unnecessary_wraps)]
41#![warn(clippy::unseparated_literal_suffix)]
42#![deny(clippy::unwrap_used)]
43#![deny(clippy::mod_module_files)]
44#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
45#![allow(clippy::uninlined_format_args)]
46#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
47#![allow(clippy::result_large_err)] // temporary workaround for arti#587
48#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
49#![allow(clippy::needless_lifetimes)] // See arti#1765
50#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
51//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
52
53// This crate used to have unsafe code to interact with various libc functions.
54// Nowadays we use pwd_grp, which is tested with miri.
55// This #[forbid] assures us that we have removed all direct unsafe libc access.
56//
57// If this crate grows some other reason to want some unsafe, it is OK to remove this,
58// subject to all the usual considerations when writing unsafe.
59#![forbid(unsafe_code)]
60
61mod dir;
62mod disable;
63mod err;
64mod file_access;
65mod imp;
66#[cfg(all(
67    target_family = "unix",
68    not(target_os = "ios"),
69    not(target_os = "android"),
70    not(target_os = "tvos")
71))]
72mod user;
73
74#[cfg(feature = "anon_home")]
75pub mod anon_home;
76#[cfg(test)]
77pub(crate) mod testing;
78pub mod walk;
79
80#[cfg(feature = "serde")]
81use serde::{Deserialize, Serialize};
82use std::{
83    fs::DirBuilder,
84    path::{Path, PathBuf},
85    sync::Arc,
86};
87
88pub use dir::CheckedDir;
89pub use disable::GLOBAL_DISABLE_VAR;
90pub use err::{format_access_bits, Error};
91pub use file_access::FileAccess;
92
93/// A result type as returned by this crate
94pub type Result<T> = std::result::Result<T, Error>;
95
96#[cfg(all(
97    target_family = "unix",
98    not(target_os = "ios"),
99    not(target_os = "android"),
100    not(target_os = "tvos")
101))]
102pub use user::{TrustedGroup, TrustedUser};
103
104/// Configuration for verifying that a file or directory is really "private".
105///
106/// By default, we mistrust everything that we can: we assume  that every
107/// directory on the filesystem is potentially misconfigured.  This object can
108/// be used to change that.
109///
110/// Once you have a working [`Mistrust`], you can call its "`check_*`" methods
111/// directly, or use [`verifier()`](Mistrust::verifier) to configure a more
112/// complicated check.
113///  
114/// See the [crate documentation](crate) for more information.
115///
116/// # Environment variables
117///
118/// The [`Mistrust`] can be configured to consider an environment variable.
119/// See [`MistrustBuilder::controlled_by_default_env_var`] and similar methods.
120///
121/// Names that seem to say "don't disable" are treated as "false". Any
122/// other value is treated as "true".  (That is, we err on the side of
123/// assuming that if you set a disable variable, you meant to disable.)
124///
125/// If the `Mistrust` is configured to use an environment variable,
126/// this environment variable typically becomes part of the application's public interface,
127/// so this library commits to a stable behaviour for parsing these variables.
128/// Specifically the following case-insensitive strings are considered "false":
129/// "false", "no", "never", "n", "0", "".
130///
131/// Examples using the default environment variable:
132///
133/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS="false"` — checks enabled
134/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS=" false "` — checks enabled
135/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS="NO"` — checks enabled
136/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS=0` — checks enabled
137/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS=` — checks enabled
138/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS=" "` — checks enabled
139/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS="true"` — checks disabled
140/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS="asdf"` — checks disabled
141///
142/// # TODO
143///
144/// *  support more kinds of trust configuration, including more trusted users,
145///    trusted groups, multiple trusted directories, etc?
146#[derive(Debug, Clone, derive_builder::Builder, Eq, PartialEq)]
147#[cfg_attr(feature = "serde", builder(derive(Debug, Serialize, Deserialize)))]
148#[cfg_attr(not(feature = "serde"), builder(derive(Debug)))]
149#[builder(build_fn(error = "Error"))]
150#[cfg_attr(feature = "serde", builder_struct_attr(serde(default)))]
151pub struct Mistrust {
152    /// If the user called [`MistrustBuilder::ignore_prefix`], what did they give us?
153    ///
154    /// (This is stored in canonical form.)
155    #[builder(
156        setter(into, strip_option),
157        field(build = "canonicalize_opt_prefix(&self.ignore_prefix)?")
158    )]
159    ignore_prefix: Option<PathBuf>,
160
161    /// Are we configured to disable all permission and ownership tests?
162    ///
163    /// (This field is present in the builder only.)
164    #[builder(setter(custom), field(type = "Option<bool>", build = "()"))]
165    dangerously_trust_everyone: (),
166
167    /// Should we check the environment to decide whether to disable permission
168    /// and ownership tests?
169    ///
170    /// (This field is present in the builder only.)
171    #[builder(setter(custom), field(type = "Option<disable::Disable>", build = "()"))]
172    #[cfg_attr(feature = "serde", builder_field_attr(serde(skip)))]
173    disable_by_environment: (),
174
175    /// Internal value combining `dangerously_trust_everyone` and
176    /// `disable_by_environment` to decide whether we're doing permissions
177    /// checks or not.
178    #[builder(setter(custom), field(build = "self.should_be_enabled()"))]
179    #[cfg_attr(feature = "serde", builder_field_attr(serde(skip)))]
180    status: disable::Status,
181
182    /// What user ID do we trust by default (if any?)
183    #[cfg(all(
184        target_family = "unix",
185        not(target_os = "ios"),
186        not(target_os = "android"),
187        not(target_os = "tvos")
188    ))]
189    #[builder(
190        setter(into),
191        field(type = "TrustedUser", build = "self.trust_user.get_uid()?")
192    )]
193    trust_user: Option<u32>,
194
195    /// What group ID do we trust by default (if any?)
196    #[cfg(all(
197        target_family = "unix",
198        not(target_os = "ios"),
199        not(target_os = "android"),
200        not(target_os = "tvos")
201    ))]
202    #[builder(
203        setter(into),
204        field(type = "TrustedGroup", build = "self.trust_group.get_gid()?")
205    )]
206    trust_group: Option<u32>,
207}
208
209/// Compute the canonical prefix for a given path prefix.
210///
211/// The funny types here are used to please derive_builder.
212#[allow(clippy::option_option)]
213fn canonicalize_opt_prefix(prefix: &Option<Option<PathBuf>>) -> Result<Option<PathBuf>> {
214    match prefix {
215        Some(Some(path)) if path.as_os_str().is_empty() => Ok(None),
216        Some(Some(path)) => Ok(Some(
217            path.canonicalize()
218                .map_err(|e| Error::inspecting(e, path))?,
219        )),
220        _ => Ok(None),
221    }
222    // TODO: Permit "not found?" .
223}
224
225impl MistrustBuilder {
226    /// Configure this `Mistrust` to trust only the admin (root) user.
227    ///
228    /// By default, both the currently running user and the root user will be
229    /// trusted.
230    ///
231    /// This option disables the default group-trust behavior as well.
232    #[cfg(all(
233        target_family = "unix",
234        not(target_os = "ios"),
235        not(target_os = "android"),
236        not(target_os = "tvos"),
237    ))]
238    pub fn trust_admin_only(&mut self) -> &mut Self {
239        self.trust_user = TrustedUser::None;
240        self.trust_group = TrustedGroup::None;
241        self
242    }
243
244    /// Configure this `Mistrust` to trust no groups at all.
245    ///
246    /// By default, we trust the group (if any) with the same name as the
247    /// current user if we are currently running as a member of that group.
248    ///
249    /// With this option set, no group is trusted, and any group-readable or
250    /// group-writable objects are treated the same as world-readable and
251    /// world-writable objects respectively.
252    #[cfg(all(
253        target_family = "unix",
254        not(target_os = "ios"),
255        not(target_os = "android"),
256        not(target_os = "tvos"),
257    ))]
258    pub fn trust_no_group_id(&mut self) -> &mut Self {
259        self.trust_group = TrustedGroup::None;
260        self
261    }
262
263    /// Configure this `Mistrust` to trust every user and every group.
264    ///
265    /// With this option set, every file and directory is treated as having
266    /// valid permissions: even world-writeable files are allowed.  File-type
267    /// checks are still performed.
268    ///
269    /// This option is mainly useful to handle cases where you want to make
270    /// these checks optional, and still use [`CheckedDir`] without having to
271    /// implement separate code paths for the "checking on" and "checking off"
272    /// cases.
273    ///
274    /// Setting this flag will supersede any value set in the environment.
275    pub fn dangerously_trust_everyone(&mut self) -> &mut Self {
276        self.dangerously_trust_everyone = Some(true);
277        self
278    }
279
280    /// Remove any ignored prefix, restoring this [`MistrustBuilder`] to a state
281    /// as if [`MistrustBuilder::ignore_prefix`] had not been called.
282    pub fn remove_ignored_prefix(&mut self) -> &mut Self {
283        self.ignore_prefix = Some(None);
284        self
285    }
286
287    /// Configure this [`MistrustBuilder`] to become disabled based on the
288    /// environment variable `var`.
289    ///
290    /// See [`Mistrust`](Mistrust#environment-variables) for details about
291    /// the handling of the environment variable.
292    ///
293    /// If `var` is not set, then we'll look at
294    /// `$FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS`.
295    pub fn controlled_by_env_var(&mut self, var: &str) -> &mut Self {
296        self.disable_by_environment = Some(disable::Disable::OnUserEnvVar(var.to_string()));
297        self
298    }
299
300    /// Like `controlled_by_env_var`, but do not override any previously set
301    /// environment settings.
302    ///
303    /// See [`Mistrust`](Mistrust#environment-variables) for details about
304    /// the handling of the environment variable.
305    ///
306    /// (The `arti-client` wants this, so that it can inform a caller-supplied
307    /// `MistrustBuilder` about its Arti-specific env var, but only if the
308    /// caller has not already provided a variable of its own. Other code
309    /// embedding `fs-mistrust` may want it too.)
310    pub fn controlled_by_env_var_if_not_set(&mut self, var: &str) -> &mut Self {
311        if self.disable_by_environment.is_none() {
312            self.controlled_by_env_var(var)
313        } else {
314            self
315        }
316    }
317
318    /// Configure this [`MistrustBuilder`] to become disabled based on the
319    /// environment variable `$FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS` only,
320    ///
321    /// See [`Mistrust`](Mistrust#environment-variables) for details about
322    /// the handling of the environment variable.
323    ///
324    /// This is the default.
325    pub fn controlled_by_default_env_var(&mut self) -> &mut Self {
326        self.disable_by_environment = Some(disable::Disable::OnGlobalEnvVar);
327        self
328    }
329
330    /// Configure this [`MistrustBuilder`] to never consult the environment to
331    /// see whether it should be disabled.
332    pub fn ignore_environment(&mut self) -> &mut Self {
333        self.disable_by_environment = Some(disable::Disable::Never);
334        self
335    }
336
337    /// Considering our settings, determine whether we should trust all users
338    /// (and thereby disable our permission checks.)
339    fn should_be_enabled(&self) -> disable::Status {
340        // If we've disabled checks in our configuration, then that settles it.
341        if self.dangerously_trust_everyone == Some(true) {
342            return disable::Status::DisableChecks;
343        }
344
345        // Otherwise, we use our "disable_by_environment" setting to see whether
346        // we should check the environment.
347        self.disable_by_environment
348            .as_ref()
349            .unwrap_or(&disable::Disable::default())
350            .should_disable_checks()
351    }
352}
353
354impl Default for Mistrust {
355    fn default() -> Self {
356        MistrustBuilder::default()
357            .build()
358            .expect("Could not build default")
359    }
360}
361
362/// An object used to perform a single check.
363///
364/// Obtained from [`Mistrust::verifier()`].
365///
366/// A `Verifier` is used when [`Mistrust::check_directory`] and
367/// [`Mistrust::make_directory`] are not sufficient for your needs.
368#[derive(Clone, Debug)]
369#[must_use]
370pub struct Verifier<'a> {
371    /// The [`Mistrust`] that was used to create this verifier.
372    mistrust: &'a Mistrust,
373
374    /// Has the user called [`Verifier::permit_readable`]?
375    readable_okay: bool,
376
377    /// Has the user called [`Verifier::all_errors`]?
378    collect_multiple_errors: bool,
379
380    /// If the user called [`Verifier::require_file`] or
381    /// [`Verifier::require_directory`], which did they call?
382    enforce_type: Type,
383
384    /// If true, we want to check all the contents of this directory as well as
385    /// the directory itself.  Requires the `walkdir` feature.
386    check_contents: bool,
387}
388
389/// A type of object that we have been told to require.
390#[derive(Debug, Clone, Copy)]
391enum Type {
392    /// A directory.
393    Dir,
394    /// A regular file.
395    File,
396    /// A directory or a regular file.
397    DirOrFile,
398    /// Absolutely anything at all.
399    Anything,
400}
401
402impl Mistrust {
403    /// Return a new [`MistrustBuilder`].
404    pub fn builder() -> MistrustBuilder {
405        MistrustBuilder::default()
406    }
407
408    /// Initialize a new default `Mistrust`.
409    ///
410    /// By default:
411    ///    *  we will inspect all directories that are used to resolve any path that is checked.
412    pub fn new() -> Self {
413        Self::default()
414    }
415
416    /// Construct a new `Mistrust` that trusts all users and all groups.
417    ///
418    /// (In effect, this `Mistrust` will have all of its permissions checks
419    /// disabled, since if all users and groups are trusted, it doesn't matter
420    /// what the permissions on any file and directory are.)
421    pub fn new_dangerously_trust_everyone() -> Self {
422        Self::builder()
423            .dangerously_trust_everyone()
424            .build()
425            .expect("Could not construct a Mistrust")
426    }
427
428    /// Create a new [`Verifier`] with this configuration, to perform a single check.
429    pub fn verifier(&self) -> Verifier<'_> {
430        Verifier {
431            mistrust: self,
432            readable_okay: false,
433            collect_multiple_errors: false,
434            enforce_type: Type::DirOrFile,
435            check_contents: false,
436        }
437    }
438
439    /// Verify that `dir` is a directory that only trusted users can read from,
440    /// list the files in,  or write to.
441    ///
442    /// If it is, and we can verify that, return `Ok(())`.  Otherwise, return
443    /// the first problem that we encountered when verifying it.
444    ///
445    /// `m.check_directory(dir)` is equivalent to
446    /// `m.verifier().require_directory().check(dir)`.  If you need different
447    /// behavior, see [`Verifier`] for more options.
448    pub fn check_directory<P: AsRef<Path>>(&self, dir: P) -> Result<()> {
449        self.verifier().require_directory().check(dir)
450    }
451
452    /// As `check_directory`, but create the directory if needed.
453    ///
454    /// `m.check_directory(dir)` is equivalent to
455    /// `m.verifier().make_directory(dir)`.  If you need different behavior, see
456    /// [`Verifier`] for more options.
457    pub fn make_directory<P: AsRef<Path>>(&self, dir: P) -> Result<()> {
458        self.verifier().make_directory(dir)
459    }
460
461    /// Return true if this `Mistrust` object has been configured to trust all
462    /// users.
463    pub(crate) fn is_disabled(&self) -> bool {
464        self.status.disabled()
465    }
466
467    /// Create a new [`FileAccess`] for reading or writing files
468    /// while enforcing the rules of this `Mistrust`.
469    pub fn file_access(&self) -> FileAccess<'_> {
470        self.verifier().file_access()
471    }
472}
473
474impl<'a> Verifier<'a> {
475    /// Create a new [`FileAccess`] for reading or writing files
476    /// while enforcing the rules of this `Verifier`.
477    pub fn file_access(self) -> FileAccess<'a> {
478        FileAccess::from_verifier(self)
479    }
480
481    /// Configure this `Verifier` to require that all paths it checks be
482    /// files (not directories).
483    pub fn require_file(mut self) -> Self {
484        self.enforce_type = Type::File;
485        self
486    }
487
488    /// Configure this `Verifier` to require that all paths it checks be
489    /// directories.
490    pub fn require_directory(mut self) -> Self {
491        self.enforce_type = Type::Dir;
492        self
493    }
494
495    /// Configure this `Verifier` to allow the paths that it checks to be
496    /// filesystem objects of any type.
497    ///
498    /// By default, the final path (after resolving all links) must be a
499    /// directory or a regular file, not (for example) a block device or a named
500    /// pipe.
501    pub fn permit_all_object_types(mut self) -> Self {
502        self.enforce_type = Type::Anything;
503        self
504    }
505
506    /// Configure this `Verifier` to permit the target files/directory to be
507    /// _readable_ by untrusted users.
508    ///
509    /// By default, we assume that the caller wants the target file or directory
510    /// to be only readable or writable by trusted users.  With this flag, we
511    /// permit the target file or directory to be readable by untrusted users,
512    /// but not writable.
513    ///
514    /// (Note that we always allow the _parent directories_ of the target to be
515    /// readable by untrusted users, since their readability does not make the
516    /// target readable.)
517    pub fn permit_readable(mut self) -> Self {
518        self.readable_okay = true;
519        self
520    }
521
522    /// Tell this `Verifier` to accumulate as many errors as possible, rather
523    /// than stopping at the first one.
524    ///
525    /// If a single error is found, that error will be returned.  Otherwise, the
526    /// resulting error type will be [`Error::Multiple`].
527    ///
528    /// # Example
529    ///
530    /// ```
531    /// # use fs_mistrust::Mistrust;
532    /// if let Err(e) = Mistrust::new().verifier().all_errors().check("/home/gardenGnostic/.gnupg/") {
533    ///    for error in e.errors() {
534    ///       println!("{}", e)
535    ///    }
536    /// }
537    /// ```
538    pub fn all_errors(mut self) -> Self {
539        self.collect_multiple_errors = true;
540        self
541    }
542
543    /// Configure this verifier so that, after checking the directory, check all
544    /// of its contents.
545    ///
546    /// Symlinks are not permitted; both files and directories are allowed. This
547    /// option implies `require_directory()`, since only a directory can have
548    /// contents.
549    ///
550    /// Requires that the `walkdir` feature is enabled.
551    #[cfg(feature = "walkdir")]
552    pub fn check_content(mut self) -> Self {
553        self.check_contents = true;
554        self.require_directory()
555    }
556
557    /// Check whether the file or directory at `path` conforms to the
558    /// requirements of this `Verifier` and the [`Mistrust`] that created it.
559    pub fn check<P: AsRef<Path>>(&self, path: P) -> Result<()> {
560        let path = path.as_ref();
561
562        // This is the powerhouse of our verifier code:
563        //
564        // See the `imp` module for actual implementation logic.
565        let mut error_iterator = self
566            .check_errors(path.as_ref())
567            .chain(self.check_content_errors(path.as_ref()));
568
569        // Collect either the first error, or all errors.
570        let opt_error: Option<Error> = if self.collect_multiple_errors {
571            error_iterator.collect()
572        } else {
573            let next = error_iterator.next();
574            drop(error_iterator); // so that "canonical" is no longer borrowed.
575            next
576        };
577
578        if let Some(err) = opt_error {
579            return Err(err);
580        }
581
582        Ok(())
583    }
584    /// Check whether `path` is a valid directory, and create it if it doesn't
585    /// exist.
586    ///
587    /// Returns `Ok` if the directory already existed or if it was just created,
588    /// and it conforms to the requirements of this `Verifier` and the
589    /// [`Mistrust`] that created it.
590    ///
591    /// Return an error if:
592    ///  * there was a permissions or ownership problem in the path or any of
593    ///    its ancestors,
594    ///  * there was a problem when creating the directory
595    ///  * after creating the directory, we found that it had a permissions or
596    ///    ownership problem.
597    pub fn make_directory<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
598        self.enforce_type = Type::Dir;
599
600        let path = path.as_ref();
601        match self.clone().check(path) {
602            Err(Error::NotFound(_)) => {}
603            Err(other_error) => return Err(other_error),
604            Ok(()) => return Ok(()), // no error; file exists.
605        }
606
607        // Looks like we got a "not found", so we're creating the path.
608        let mut bld = DirBuilder::new();
609        #[cfg(target_family = "unix")]
610        {
611            use std::os::unix::fs::DirBuilderExt;
612            bld.mode(0o700);
613        }
614        bld.recursive(true)
615            .create(path)
616            .map_err(|e| Error::CreatingDir(Arc::new(e)))?;
617
618        // We built the path!  But for paranoia's sake, check it again.
619        self.check(path)
620    }
621
622    /// Check whether `path` is a directory conforming to the requirements of
623    /// this `Verifier` and the [`Mistrust`] that created it.
624    ///
625    /// If it is, then return a new [`CheckedDir`] that can be used to securely access
626    /// the contents of this directory.  
627    pub fn secure_dir<P: AsRef<Path>>(self, path: P) -> Result<CheckedDir> {
628        let path = path.as_ref();
629        self.clone().require_directory().check(path)?;
630        CheckedDir::new(&self, path)
631    }
632
633    /// Check whether `path` is a directory conforming to the requirements of
634    /// this `Verifier` and the [`Mistrust`] that created it.
635    ///
636    /// If successful, then return a new [`CheckedDir`] that can be used to
637    /// securely access the contents of this directory.  
638    pub fn make_secure_dir<P: AsRef<Path>>(self, path: P) -> Result<CheckedDir> {
639        let path = path.as_ref();
640        self.clone().require_directory().make_directory(path)?;
641        CheckedDir::new(&self, path)
642    }
643}
644
645#[cfg(test)]
646mod test {
647    // @@ begin test lint list maintained by maint/add_warning @@
648    #![allow(clippy::bool_assert_comparison)]
649    #![allow(clippy::clone_on_copy)]
650    #![allow(clippy::dbg_macro)]
651    #![allow(clippy::mixed_attributes_style)]
652    #![allow(clippy::print_stderr)]
653    #![allow(clippy::print_stdout)]
654    #![allow(clippy::single_char_pattern)]
655    #![allow(clippy::unwrap_used)]
656    #![allow(clippy::unchecked_duration_subtraction)]
657    #![allow(clippy::useless_vec)]
658    #![allow(clippy::needless_pass_by_value)]
659    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
660    use super::*;
661    use testing::{mistrust_build, Dir, MistrustOp};
662
663    #[cfg(target_family = "unix")]
664    use testing::LinkType;
665
666    #[cfg(target_family = "unix")]
667    #[test]
668    fn simple_cases() {
669        let d = Dir::new();
670        d.dir("a/b/c");
671        d.dir("e/f/g");
672        d.chmod("a", 0o755);
673        d.chmod("a/b", 0o755);
674        d.chmod("a/b/c", 0o700);
675        d.chmod("e", 0o755);
676        d.chmod("e/f", 0o777);
677        d.link_rel(LinkType::Dir, "a/b/c", "d");
678
679        let m = mistrust_build(&[
680            MistrustOp::IgnorePrefix(d.canonical_root()),
681            MistrustOp::TrustNoGroupId(),
682        ]);
683
684        // /a/b/c should be fine...
685        m.check_directory(d.path("a/b/c")).unwrap();
686        // /e/f/g should not.
687        let e = m.check_directory(d.path("e/f/g")).unwrap_err();
688        assert!(matches!(e, Error::BadPermission(_, 0o777, 0o022)));
689        assert_eq!(e.path().unwrap(), d.path("e/f").canonicalize().unwrap());
690
691        m.check_directory(d.path("d")).unwrap();
692    }
693
694    #[cfg(target_family = "unix")]
695    #[test]
696    fn admin_only() {
697        use std::os::unix::prelude::MetadataExt;
698
699        let d = Dir::new();
700        d.dir("a/b");
701        d.chmod("a", 0o700);
702        d.chmod("a/b", 0o700);
703
704        if d.path("a/b").metadata().unwrap().uid() == 0 {
705            // Nothing to do here; we _are_ root.
706            return;
707        }
708
709        // With normal settings should be okay...
710        let m = mistrust_build(&[MistrustOp::IgnorePrefix(d.canonical_root())]);
711        m.check_directory(d.path("a/b")).unwrap();
712
713        // With admin_only, it'll fail.
714        let m = mistrust_build(&[
715            MistrustOp::IgnorePrefix(d.canonical_root()),
716            MistrustOp::TrustAdminOnly(),
717        ]);
718
719        let err = m.check_directory(d.path("a/b")).unwrap_err();
720        assert!(matches!(err, Error::BadOwner(_, _)));
721        assert_eq!(err.path().unwrap(), d.path("a").canonicalize().unwrap());
722    }
723
724    #[test]
725    fn want_type() {
726        let d = Dir::new();
727        d.dir("a");
728        d.file("b");
729        d.chmod("a", 0o700);
730        d.chmod("b", 0o600);
731
732        let m = mistrust_build(&[
733            MistrustOp::IgnorePrefix(d.canonical_root()),
734            MistrustOp::TrustNoGroupId(),
735        ]);
736
737        // If we insist stuff is its own type, it works fine.
738        m.verifier().require_directory().check(d.path("a")).unwrap();
739        m.verifier().require_file().check(d.path("b")).unwrap();
740
741        // If we insist on a different type, we hit an error.
742        let e = m
743            .verifier()
744            .require_directory()
745            .check(d.path("b"))
746            .unwrap_err();
747        assert!(matches!(e, Error::BadType(_)));
748        assert_eq!(e.path().unwrap(), d.path("b").canonicalize().unwrap());
749
750        let e = m.verifier().require_file().check(d.path("a")).unwrap_err();
751        assert!(matches!(e, Error::BadType(_)));
752        assert_eq!(e.path().unwrap(), d.path("a").canonicalize().unwrap());
753
754        // TODO: Possibly, make sure that a special file matches neither.
755    }
756
757    #[cfg(target_family = "unix")]
758    #[test]
759    fn readable_ok() {
760        let d = Dir::new();
761        d.dir("a/b");
762        d.file("a/b/c");
763        d.chmod("a", 0o750);
764        d.chmod("a/b", 0o750);
765        d.chmod("a/b/c", 0o640);
766
767        let m = mistrust_build(&[
768            MistrustOp::IgnorePrefix(d.canonical_root()),
769            MistrustOp::TrustNoGroupId(),
770        ]);
771
772        // These will fail, since the file or directory is readable.
773        let e = m.verifier().check(d.path("a/b")).unwrap_err();
774        assert!(matches!(e, Error::BadPermission(..)));
775        assert_eq!(e.path().unwrap(), d.path("a/b").canonicalize().unwrap());
776        let e = m.verifier().check(d.path("a/b/c")).unwrap_err();
777        assert!(matches!(e, Error::BadPermission(..)));
778        assert_eq!(e.path().unwrap(), d.path("a/b/c").canonicalize().unwrap());
779
780        // Now allow readable targets.
781        m.verifier().permit_readable().check(d.path("a/b")).unwrap();
782        m.verifier()
783            .permit_readable()
784            .check(d.path("a/b/c"))
785            .unwrap();
786    }
787
788    #[cfg(target_family = "unix")]
789    #[test]
790    fn multiple_errors() {
791        let d = Dir::new();
792        d.dir("a/b");
793        d.chmod("a", 0o700);
794        d.chmod("a/b", 0o700);
795
796        let m = mistrust_build(&[
797            MistrustOp::IgnorePrefix(d.canonical_root()),
798            MistrustOp::TrustNoGroupId(),
799        ]);
800
801        // Only one error occurs, so we get that error.
802        let e = m
803            .verifier()
804            .all_errors()
805            .check(d.path("a/b/c"))
806            .unwrap_err();
807        assert!(matches!(e, Error::NotFound(_)));
808        assert_eq!(1, e.errors().count());
809
810        // Introduce a second error...
811        d.chmod("a/b", 0o770);
812        let e = m
813            .verifier()
814            .all_errors()
815            .check(d.path("a/b/c"))
816            .unwrap_err();
817        assert!(matches!(e, Error::Multiple(_)));
818        let errs: Vec<_> = e.errors().collect();
819        assert_eq!(2, errs.len());
820        assert!(matches!(&errs[0], Error::BadPermission(..)));
821        assert!(matches!(&errs[1], Error::NotFound(_)));
822    }
823
824    #[cfg(target_family = "unix")]
825    #[test]
826    fn sticky() {
827        let d = Dir::new();
828        d.dir("a/b/c");
829        d.chmod("a", 0o777);
830        d.chmod("a/b", 0o755);
831        d.chmod("a/b/c", 0o700);
832
833        let m = mistrust_build(&[MistrustOp::IgnorePrefix(d.canonical_root())]);
834
835        // `a` is world-writable, so the first check will fail.
836        m.check_directory(d.path("a/b/c")).unwrap_err();
837
838        // Now `a` is world-writable _and_ sticky, so the check should succeed.
839        d.chmod("a", 0o777 | crate::imp::STICKY_BIT);
840
841        m.check_directory(d.path("a/b/c")).unwrap();
842
843        // Make sure we got the right definition!
844        #[allow(clippy::useless_conversion)]
845        {
846            assert_eq!(crate::imp::STICKY_BIT, u32::from(libc::S_ISVTX));
847        }
848    }
849
850    #[cfg(target_family = "unix")]
851    #[test]
852    fn trust_gid() {
853        use std::os::unix::prelude::MetadataExt;
854        let d = Dir::new();
855        d.dir("a/b");
856        d.chmod("a", 0o770);
857        d.chmod("a/b", 0o770);
858
859        let m = mistrust_build(&[
860            MistrustOp::IgnorePrefix(d.canonical_root()),
861            MistrustOp::TrustNoGroupId(),
862        ]);
863
864        // By default, we shouldn't be accept this directory, since it is
865        // group-writable.
866        let e = m.check_directory(d.path("a/b")).unwrap_err();
867        assert!(matches!(e, Error::BadPermission(..)));
868
869        // But we can make the group trusted, which will make it okay for the
870        // directory to be group-writable.
871        let gid = d.path("a/b").metadata().unwrap().gid();
872
873        let m = mistrust_build(&[
874            MistrustOp::IgnorePrefix(d.canonical_root()),
875            MistrustOp::TrustGroup(gid),
876        ]);
877
878        m.check_directory(d.path("a/b")).unwrap();
879
880        // OTOH, if we made a _different_ group trusted, it'll fail.
881        let m = mistrust_build(&[
882            MistrustOp::IgnorePrefix(d.canonical_root()),
883            MistrustOp::TrustGroup(gid ^ 1),
884        ]);
885
886        let e = m.check_directory(d.path("a/b")).unwrap_err();
887        assert!(matches!(e, Error::BadPermission(..)));
888    }
889
890    #[test]
891    fn make_directory() {
892        let d = Dir::new();
893        d.dir("a/b");
894
895        let m = mistrust_build(&[MistrustOp::IgnorePrefix(d.canonical_root())]);
896
897        #[cfg(target_family = "unix")]
898        {
899            // Try once with bad permissions.
900            d.chmod("a", 0o777);
901            let e = m.make_directory(d.path("a/b/c/d")).unwrap_err();
902            assert!(matches!(e, Error::BadPermission(..)));
903
904            // Now make the permissions correct.
905            d.chmod("a", 0o0700);
906            d.chmod("a/b", 0o0700);
907        }
908
909        // Make the directory!
910        m.make_directory(d.path("a/b/c/d")).unwrap();
911
912        // Make sure it exists and has good permissions.
913        m.check_directory(d.path("a/b/c/d")).unwrap();
914
915        // Try make_directory again and make sure _that_ succeeds.
916        m.make_directory(d.path("a/b/c/d")).unwrap();
917    }
918
919    #[cfg(target_family = "unix")]
920    #[cfg(feature = "walkdir")]
921    #[test]
922    fn check_contents() {
923        let d = Dir::new();
924        d.dir("a/b/c");
925        d.file("a/b/c/d");
926        d.chmod("a", 0o700);
927        d.chmod("a/b", 0o700);
928        d.chmod("a/b/c", 0o755);
929        d.chmod("a/b/c/d", 0o666);
930
931        let m = mistrust_build(&[MistrustOp::IgnorePrefix(d.canonical_root())]);
932
933        // A check should work...
934        m.check_directory(d.path("a/b")).unwrap();
935
936        // But we get an error if we check the contents.
937        let e = m
938            .verifier()
939            .all_errors()
940            .check_content()
941            .check(d.path("a/b"))
942            .unwrap_err();
943        assert_eq!(1, e.errors().count());
944
945        // We only expect an error on the _writable_ contents: the _readable_
946        // a/b/c is okay.
947        assert_eq!(e.path().unwrap(), d.path("a/b/c/d"));
948    }
949
950    #[test]
951    fn trust_everyone() {
952        let d = Dir::new();
953        d.dir("a/b/c");
954        d.file("a/b/c/d");
955        d.chmod("a", 0o777);
956        d.chmod("a/b", 0o777);
957        d.chmod("a/b/c", 0o777);
958        d.chmod("a/b/c/d", 0o666);
959
960        let m = mistrust_build(&[MistrustOp::DangerouslyTrustEveryone()]);
961
962        // This is fine.
963        m.check_directory(d.path("a/b/c")).unwrap();
964        // This isn't a directory!
965        let err = m.check_directory(d.path("a/b/c/d")).unwrap_err();
966        assert!(matches!(err, Error::BadType(_)));
967
968        // But it _is_ a file.
969        m.verifier()
970            .require_file()
971            .check(d.path("a/b/c/d"))
972            .unwrap();
973    }
974
975    #[test]
976    fn default_mistrust() {
977        // we can't test a mistrust without ignore_prefix, but we should make sure that we can build one.
978        let _m = Mistrust::default();
979    }
980
981    // TODO: Write far more tests.
982    // * Can there be a test for a failed readlink()?  I can't see an easy way
983    //   to provoke that without trying to make a time-of-check/time-of-use race
984    //   condition, since we stat the link before we call readlink on it.
985    // * Can there be a test for a failing call to std::env::current_dir?  Seems
986    //   hard to provoke without calling set_current_dir(), which isn't good
987    //   manners in a test.
988}