Skip to main content

endpoint_sec/
lib.rs

1//! Safe bindings for the [Endpoint Security Framework][esf] for Apple targets (macOS).
2//!
3//! The [`sys`] module contains the raw bindings since several types are publicly exported from there.
4//!
5//! At runtime, users should call [`version::set_runtime_version()`] before anything else, to indicate
6//! on which macOS version the app is running on.
7//!
8//! The entry point is the [`Client`] type, which is a wrapper around [`es_client_t`][sys::es_client_t],
9//! with the [`Client::new()`] method.
10//!
11//! After a `Client` has been created, [events][sys::es_event_type_t] can be subscribed to
12//! using [`Client::subscribe()`]. Each time Endpoint Security gets an event that is part of the
13//! subscribptions for your client, it will call the handler that was given to `Client::new()` with
14//! the [message][Message] associated to the event. Note that `AUTH` events have an associated
15//! deadline before which your handler must give a response else your client may be killed by macOS
16//! to avoid stalling for the user.
17//!
18//! [esf]: https://developer.apple.com/documentation/endpointsecurity
19
20#![cfg(target_os = "macos")]
21#![cfg_attr(docsrs, feature(doc_cfg))]
22#![allow(clippy::bool_comparison)]
23#![warn(
24    missing_docs,
25    unused_crate_dependencies,
26    clippy::missing_safety_doc,
27    unreachable_pub,
28    clippy::missing_docs_in_private_items,
29    rustdoc::bare_urls,
30    rustdoc::broken_intra_doc_links
31)]
32
33// Reexports [`endpoint_sec_sys`]
34pub use endpoint_sec_sys as sys;
35#[cfg(test)]
36use trybuild as _;
37
38/// Our wrappers around Endpoint Security events cannot easily implement [`Debug`]: they contain
39/// a reference to the original value (and sometimes the version). Since the structs behind the
40/// references can change shape based on macOS' versions, we cannot rely on the one compiled in
41/// our Rust bindings. What's more, some fields are only available in some versions of Endpoint
42/// Security, others are behind pointers and associated with another (eg, array + len). This macro
43/// makes it easier to implement [`Debug`] by simply passing the type and the functions to use for
44/// the `Debug` impl. See examples of usage in the modules below.
45macro_rules! impl_debug_eq_hash_with_functions {
46    ($ty:ident$(<$lt: lifetime>)? $(with $version:ident)?; $($(#[$fmeta: meta])? $fname:ident),* $(,)?) =>  {
47        impl $(<$lt>)? ::core::fmt::Debug for $ty $(<$lt>)? {
48            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
49                let mut d = f.debug_struct(::core::stringify!($ty));
50                $( d.field("version", &self.$version); )?
51                $( $(#[$fmeta])? d.field(::core::stringify!($fname), &self.$fname()); )*
52                d.finish()
53            }
54        }
55
56        impl $(<$lt>)? ::core::cmp::PartialEq for $ty $(<$lt>)? {
57            #[allow(unused_variables)]
58            fn eq(&self, other: &Self) -> bool {
59                $( if ::core::cmp::PartialEq::ne(&self.$version, &other.$version) { return false } )?
60                $( $(#[$fmeta])? if ::core::cmp::PartialEq::ne(&self.$fname(), &other.$fname()) { return false; } )*
61                true
62            }
63        }
64
65        impl $(<$lt>)? ::core::cmp::Eq for $ty $(<$lt>)? {}
66
67        impl $(<$lt>)? ::core::hash::Hash for $ty $(<$lt>)? {
68            #[allow(unused_variables)]
69            fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
70                $( ::core::hash::Hash::hash(&self.$version, state); )?
71                $( $(#[$fmeta])? ::core::hash::Hash::hash(&self.$fname(), state); )*
72            }
73        }
74
75    };
76}
77
78/// Helper macro to generate all necessary version checks for a function call.
79macro_rules! versioned_call {
80    // It's not possible to use `feature = ::std::concat(...)` so we need to pass both forms.
81    (if cfg!($cfg: meta) && version >= ($major:literal, $minor:literal, $patch:literal) { $($if_tt:tt)* } $(else { $($else_tt:tt)* })?) => {
82        if ::std::cfg!($cfg) && $crate::version::is_version_or_more($major, $minor, $patch) {
83            #[cfg($cfg)]
84            { $($if_tt)* }
85            #[cfg(not($cfg))]
86            // Safety: the cfg was checked just above
87            unsafe { ::std::hint::unreachable_unchecked() }
88        } $( else {
89            $($else_tt)*
90        } )?
91    };
92}
93
94// Publicly reexported modules
95#[cfg(feature = "macos_10_15_1")]
96mod acl;
97mod action;
98mod audit;
99mod client;
100mod event;
101mod message;
102mod mute;
103// Not public
104mod utils;
105
106#[cfg(feature = "macos_10_15_1")]
107pub use acl::*;
108pub use action::*;
109pub use audit::*;
110pub use client::*;
111pub use event::*;
112pub use message::*;
113pub use mute::*;
114
115/// Helper module to avoid implementing version detection in this crate and make testing easier
116/// by telling the crate its on a lower version than the real one.
117pub mod version {
118    use std::sync::atomic::{AtomicU64, Ordering};
119
120    /// macOS major version
121    static MAJOR: AtomicU64 = AtomicU64::new(10);
122    /// macOS minor version
123    static MINOR: AtomicU64 = AtomicU64::new(15);
124    /// macOS patch version
125    static PATCH: AtomicU64 = AtomicU64::new(0);
126
127    /// Setup the runtime version of macOS, detected outside of this library.
128    ///
129    /// Conservatively, this library assumes the default is 10.15.0 and will refuse to use functions
130    /// that only became available in later version of macOS and Endpoint Security.
131    ///
132    /// Methods on [`Client`][super::Client] will check this version when calling a function only
133    /// available in macOS 11+ for example.
134    ///
135    /// # Panics
136    ///
137    /// Will panic if attempting to set a version below 10.15.0.
138    pub fn set_runtime_version(major: u64, minor: u64, patch: u64) {
139        if major < 10 || (major == 10 && minor < 15) {
140            panic!("Endpoint Security cannot run on versions inferiors to 10.15.0");
141        }
142
143        MAJOR.store(major, Ordering::Release);
144        MINOR.store(minor, Ordering::Release);
145        PATCH.store(patch, Ordering::Release);
146    }
147
148    /// `true` if the version setup in [`set_runtime_version()`] is at least the given
149    /// `major.minor.patch` here.
150    pub fn is_version_or_more(major: u64, minor: u64, patch: u64) -> bool {
151        let current_major = MAJOR.load(Ordering::Acquire);
152        let current_minor = MINOR.load(Ordering::Acquire);
153        let current_patch = PATCH.load(Ordering::Acquire);
154
155        (current_major, current_minor, current_patch) >= (major, minor, patch)
156    }
157
158    #[cfg(test)]
159    mod tests {
160        use super::*;
161
162        #[test]
163        #[should_panic(expected = "Endpoint Security cannot run on versions inferiors to 10.15.0")]
164        fn test_cannot_set_version_major_under_10() {
165            set_runtime_version(9, 16, 2);
166        }
167
168        #[test]
169        #[should_panic(expected = "Endpoint Security cannot run on versions inferiors to 10.15.0")]
170        fn test_cannot_set_version_minor_under_10_15() {
171            set_runtime_version(10, 14, 0);
172        }
173
174        #[test]
175        fn test_is_version_or_more_with_set_runtime() {
176            set_runtime_version(10, 15, 0);
177
178            assert!(is_version_or_more(10, 14, 99));
179            assert!(is_version_or_more(9, 15, 0));
180            assert!(is_version_or_more(9, 14, 1));
181            assert!(is_version_or_more(10, 15, 0));
182
183            assert!(!is_version_or_more(12, 3, 1));
184            assert!(!is_version_or_more(13, 3, 2));
185            assert!(!is_version_or_more(14, 5, 4));
186            assert!(!is_version_or_more(15, 0, 0));
187
188            set_runtime_version(13, 3, 2);
189
190            assert!(is_version_or_more(12, 3, 1));
191            assert!(is_version_or_more(13, 3, 2));
192            assert!(!is_version_or_more(14, 5, 4));
193            assert!(!is_version_or_more(15, 0, 0));
194        }
195    }
196}