├── .gitignore ├── examples ├── Extension.entitlements ├── process_monitor.rs └── signal_intercept.rs ├── Cargo.toml ├── README.md ├── LICENSE └── src ├── parsers.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | *.swp 4 | -------------------------------------------------------------------------------- /examples/Extension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.application-identifier 6 | 5QYJ6C8ZNT.* 7 | com.apple.developer.endpoint-security.client 8 | 9 | com.apple.developer.team-identifier 10 | 5QYJ6C8ZNT 11 | 12 | 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "endpointsecurity" 3 | version = "0.3.0" 4 | authors = ["Mitchell Grenier "] 5 | edition = "2018" 6 | description = "Nice (ish) bindings for the EndpointSecurity framework on macOS" 7 | license-file = "LICENSE" 8 | keywords = ["security", "macos", "endpoint"] 9 | readme = "README.md" 10 | homepage = "https://github.com/obelisk/endpointsecurity" 11 | 12 | [dependencies] 13 | crossbeam-channel = "0.5" 14 | block = "0.1.6" 15 | env_logger = "0.7.1" 16 | libc = "0.2.0" 17 | log = "0.4.8" 18 | serde = {version = "1", features = ["derive"]} 19 | 20 | [dev-dependencies] 21 | serde_json = "1" 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EndpointSecurity in Rust 2 | 3 | This is a crate I wrote to power building EndpointSecurity products in Rust. This is still very much in active development and subject to significant changes (though the existing pub api I would expect to stay more or less the same). 4 | 5 | EsClient is marked Send and Sync because to the best of my knowledge, it is. There are locks around using it inside the API because I don't know if EndpointSecurity is threadsafe. Your mileage may vary. 6 | 7 | Not all calls are implemented, only the ones I've needed so far and some of the ones that are implemented are incomplete (most of the important stuff is there though). 8 | 9 | Licenced under MIT because free software is important. 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mitchell Grenier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/process_monitor.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate log; 2 | 3 | use endpointsecurity::*; 4 | use crossbeam_channel::unbounded as channel; 5 | 6 | fn main() { 7 | env_logger::init(); 8 | info!("Starting example process monitor"); 9 | let (es_message_tx, es_message_rx) = channel(); 10 | 11 | let client = match create_es_client(es_message_tx.clone()) { 12 | Ok(client) => client, 13 | Err(e) => { 14 | error!("{:?}: {}", e.error_type, e.details); 15 | return; 16 | } 17 | }; 18 | 19 | if !client.set_subscriptions_to(&vec![SupportedEsEvent::NotifyExec, SupportedEsEvent::NotifyFork]) { 20 | error!("Could not subscribe to NotifyExec event (not sure why)"); 21 | return; 22 | } 23 | 24 | loop { 25 | let message = match es_message_rx.recv() { 26 | Ok(v) => v, 27 | Err(e) => { 28 | error!("Error receiving new event but will continue anyway: {}", e); 29 | continue; 30 | } 31 | }; 32 | 33 | match &message.event { 34 | EsEvent::NotifyExec(event) => { 35 | match serde_json::to_string(&event) { 36 | Ok(json) => println!("{}", json), 37 | Err(e) => error!("Error serializing event: {}", e) 38 | } 39 | //println!("Type: Exec, PID: {}, Path: {}, CDHash: {}, Args: {}", event.target.pid, event.target.executable.path, event.target.cdhash, event.args.join(" ")); 40 | }, 41 | EsEvent::NotifyFork(event) => { 42 | match serde_json::to_string(&event) { 43 | Ok(json) => println!("{}", json), 44 | Err(e) => error!("Error serializing event: {}", e) 45 | } 46 | //println!("Type: Fork, PID: {}, Path: {}, CDHash: {}", event.child.pid, event.child.executable.path, event.child.cdhash); 47 | }, 48 | _ => { 49 | continue; 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /examples/signal_intercept.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate log; 2 | 3 | use endpointsecurity::*; 4 | use crossbeam_channel::unbounded as channel; 5 | 6 | fn main() { 7 | env_logger::init(); 8 | info!("Starting example signal intercept handler"); 9 | 10 | let (es_message_tx, es_message_rx) = channel(); 11 | 12 | let client = match create_es_client(es_message_tx.clone()) { 13 | Ok(client) => client, 14 | Err(e) => { 15 | error!("{:?}: {}", e.error_type, e.details); 16 | return; 17 | } 18 | }; 19 | 20 | if !client.set_subscriptions_to(&vec![SupportedEsEvent::AuthSignal]) { 21 | error!("Could not subscribe to AuthSignal event (not sure why)"); 22 | return; 23 | } 24 | 25 | loop { 26 | let message = match es_message_rx.recv() { 27 | Ok(v) => v, 28 | Err(e) => { 29 | error!("Error receiving new event but will continue anyway: {}", e); 30 | continue; 31 | } 32 | }; 33 | 34 | // We don't cache the result here for demonstation purposes. In practice you should 35 | // cache whenever possible but here we don't so you can see every denial instead of 36 | // just the first one 37 | match &message.event { 38 | EsEvent::AuthSignal(event) => { 39 | // Backout of all signals that don't affect EsClients as quickly as possible 40 | // to reduce impact to system responsiveness 41 | if !event.target.is_es_client { 42 | client.respond_to_auth_event(&message, &EsAuthResult::Allow, &EsCacheResult::No); 43 | continue; 44 | } 45 | 46 | // My signing ID, don't let signals reach my EsClients 47 | if event.target.team_id == "5QYJ6C8ZNT" { 48 | println!("Received a signal to my EsClient, disallowing that!"); 49 | client.respond_to_auth_event(&message, &EsAuthResult::Deny, &EsCacheResult::No); 50 | } 51 | 52 | // This is a signal to someone else's EsClient. Don't touch it 53 | client.respond_to_auth_event(&message, &EsAuthResult::Allow, &EsCacheResult::No); 54 | }, 55 | _ => { 56 | continue; 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/parsers.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use crate::{ 3 | audit_token_t, 4 | es_action_type_t_ES_ACTION_TYPE_AUTH as ES_ACTION_TYPE_AUTH, 5 | es_action_type_t_ES_ACTION_TYPE_NOTIFY as ES_ACTION_TYPE_NOTIFY, 6 | es_events_t, 7 | es_exec_arg, 8 | es_exec_arg_count, 9 | es_file_t, 10 | es_message_t, 11 | es_message_t__bindgen_ty_1, 12 | es_process_t, 13 | es_string_token_t, 14 | pid_t, 15 | }; 16 | 17 | // Non Event Structures 18 | use crate::{ 19 | EsAction, 20 | EsActionType, 21 | EsEvent, 22 | EsEventId, // I know this is inconsistent but I don't have a better name for this struct 23 | EsFile, 24 | EsMessage, 25 | EsRenameDestination, 26 | EsRenameDestinationNewPath, 27 | EsResult, 28 | EsResultType, 29 | EsResultNotifyResult, 30 | EsDestinationType, 31 | EsProcess, 32 | SupportedEsEvent, 33 | }; 34 | 35 | // Event Structures 36 | use crate::{ 37 | EsEventClose, 38 | EsEventExec, 39 | EsEventFork, 40 | EsEventKextload, 41 | EsEventKextunload, 42 | EsEventLink, 43 | EsEventOpen, 44 | EsEventReadDir, 45 | EsEventRename, 46 | EsEventSignal, 47 | EsEventUnlink, 48 | }; 49 | 50 | use crate::raw_event_to_supportedesevent; 51 | 52 | extern "C" { 53 | pub fn audit_token_to_pid(audit_token: audit_token_t) -> pid_t; 54 | } 55 | 56 | /// Take in an es_event_t from the EndpointSecurity Framework, 57 | /// and parse it into a safe Rust structure. 58 | fn parse_es_action(action: es_message_t__bindgen_ty_1, action_type: &EsActionType) -> Option { 59 | Some(match action_type { 60 | EsActionType::Auth => EsAction::Auth(EsEventId{ 61 | reserved: unsafe {action.auth.reserved}, 62 | }), 63 | EsActionType::Notify => EsAction::Notify(EsResult { 64 | result_type: { 65 | match unsafe {action.notify.result_type} { 66 | 0 => EsResultType::Auth, 67 | 1 => EsResultType::Flags, 68 | _ => { 69 | error!("Result Type is broken"); 70 | return None; // At time of writing these are the only types 71 | } 72 | } 73 | }, 74 | result: EsResultNotifyResult { 75 | flags: unsafe { action.notify.result.flags }, 76 | } 77 | }) 78 | }) 79 | } 80 | 81 | /// Take in an es_event_t from the EndpointSecurity Framework, 82 | /// and parse it into a safe Rust structure. 83 | fn parse_es_event(event_type: SupportedEsEvent, event: es_events_t, action_type: &EsActionType) -> EsEvent { 84 | unsafe { 85 | match event_type { 86 | SupportedEsEvent::AuthExec | SupportedEsEvent::NotifyExec => { 87 | let target = event.exec.target; 88 | let argc = es_exec_arg_count(&event.exec as *const _); 89 | let mut argv = vec![]; 90 | argv.reserve(argc as usize); 91 | let mut x = 0; 92 | while x < argc { 93 | argv.push(parse_es_string_token(es_exec_arg(&event.exec as *const _, x as u32))); 94 | x += 1; 95 | } 96 | 97 | let event = EsEventExec { 98 | target: parse_es_process(&*target), 99 | args: argv, 100 | }; 101 | 102 | match action_type { 103 | EsActionType::Notify => EsEvent::NotifyExec(event), 104 | EsActionType::Auth => EsEvent::AuthExec(event), 105 | } 106 | }, 107 | SupportedEsEvent::AuthOpen | SupportedEsEvent::NotifyOpen => { 108 | let file = event.open; 109 | let event = EsEventOpen { 110 | fflag: file.fflag as u32, 111 | file: parse_es_file(file.file), 112 | }; 113 | match action_type { 114 | EsActionType::Notify => EsEvent::NotifyOpen(event), 115 | EsActionType::Auth => EsEvent::AuthOpen(event), 116 | } 117 | }, 118 | SupportedEsEvent::AuthKextload | SupportedEsEvent::NotifyKextload => { 119 | let load = event.kextload; 120 | let event = EsEventKextload { 121 | identifier: parse_es_string_token(load.identifier), 122 | }; 123 | match action_type { 124 | EsActionType::Notify => EsEvent::NotifyKextload(event), 125 | EsActionType::Auth => EsEvent::AuthKextload(event), 126 | } 127 | }, 128 | SupportedEsEvent::AuthSignal | SupportedEsEvent::NotifySignal => { 129 | let target = event.signal.target; 130 | let event = EsEventSignal { 131 | signal: event.signal.sig, 132 | target: parse_es_process(&*target), 133 | }; 134 | match action_type { 135 | EsActionType::Notify => EsEvent::NotifySignal(event), 136 | EsActionType::Auth => EsEvent::AuthSignal(event), 137 | } 138 | }, 139 | SupportedEsEvent::AuthUnlink | SupportedEsEvent::NotifyUnlink => { 140 | let target = event.unlink.target; 141 | let parent_dir = event.unlink.target; 142 | let event = EsEventUnlink { 143 | target: parse_es_file(target), 144 | parent_dir: parse_es_file(parent_dir), 145 | }; 146 | match action_type { 147 | EsActionType::Notify => EsEvent::NotifyUnlink(event), 148 | EsActionType::Auth => EsEvent::AuthUnlink(event), 149 | } 150 | }, 151 | SupportedEsEvent::AuthLink | SupportedEsEvent::NotifyLink => { 152 | let event = EsEventLink { 153 | source: parse_es_file(event.link.source), 154 | target_dir: parse_es_file(event.link.target_dir), 155 | target_filename: parse_es_string_token(event.link.target_filename), 156 | }; 157 | match action_type { 158 | EsActionType::Notify => EsEvent::NotifyLink(event), 159 | EsActionType::Auth => EsEvent::AuthLink(event), 160 | } 161 | }, 162 | SupportedEsEvent::AuthRename | SupportedEsEvent::NotifyRename => { 163 | let event = EsEventRename { 164 | source: parse_es_file(event.rename.source), 165 | destination_type: match event.rename.destination_type { 166 | 0 => EsDestinationType::ExistingFile, 167 | 1 => EsDestinationType::NewPath, 168 | _ => EsDestinationType::Unknown, 169 | }, 170 | destination: EsRenameDestination { 171 | existing_file: parse_es_file(event.rename.destination.existing_file), 172 | new_path: EsRenameDestinationNewPath { 173 | dir: parse_es_file(event.rename.destination.new_path.dir), 174 | filename: parse_es_string_token(event.rename.destination.new_path.filename), 175 | }, 176 | } 177 | }; 178 | match action_type { 179 | EsActionType::Notify => EsEvent::NotifyRename(event), 180 | EsActionType::Auth => EsEvent::AuthRename(event), 181 | } 182 | }, 183 | SupportedEsEvent::AuthReadDir | SupportedEsEvent::NotifyReadDir => { 184 | let event = EsEventReadDir { 185 | target: parse_es_file(event.readdir.target), 186 | }; 187 | match action_type { 188 | EsActionType::Notify => EsEvent::NotifyReadDir(event), 189 | EsActionType::Auth => EsEvent::AuthReadDir(event), 190 | } 191 | }, 192 | SupportedEsEvent::NotifyFork => { 193 | EsEvent::NotifyFork(EsEventFork { 194 | child: parse_es_process(&*event.fork.child), 195 | }) 196 | }, 197 | SupportedEsEvent::NotifyKextunload => { 198 | EsEvent::NotifyKextunload(EsEventKextunload { 199 | identifier: parse_es_string_token(event.kextunload.identifier), 200 | }) 201 | }, 202 | SupportedEsEvent::NotifyClose => { 203 | EsEvent::NotifyClose(EsEventClose { 204 | modified: event.close.modified, 205 | target: parse_es_file(event.close.target), 206 | }) 207 | }, 208 | } 209 | } 210 | } 211 | 212 | /// Take in an es_file_t from the EndpointSecurity Framework, 213 | /// and parse it into a safe Rust structure. 214 | fn parse_es_file(file: *mut es_file_t) -> EsFile { 215 | let f = unsafe {*file}; 216 | EsFile { 217 | path: unsafe { CStr::from_ptr(f.path.data).to_str().unwrap().to_owned() }, 218 | path_truncated: { f.path_truncated }, 219 | } 220 | } 221 | 222 | /// Take in an es_message_t from the EndpointSecurity Framework, 223 | /// and parse it into a safe Rust structure. 224 | pub fn parse_es_message(message: *mut es_message_t) -> Result { 225 | let message = unsafe {&*message}; 226 | let process = unsafe {&*(message.process)}; 227 | let action_type = match message.action_type { 228 | ES_ACTION_TYPE_AUTH => EsActionType::Auth, 229 | ES_ACTION_TYPE_NOTIFY => EsActionType::Notify, 230 | _ => return Err("Couldn't parse action_type"), // At time of writing these are the only two ways 231 | }; 232 | 233 | let event = if let Some(event) = raw_event_to_supportedesevent(message.event_type as u64) { 234 | parse_es_event(event, message.event, &action_type) 235 | } else { 236 | error!("Error in this event type: {}", message.event_type as u64); 237 | return Err("Could not parse this event type"); 238 | }; 239 | 240 | Ok(EsMessage { 241 | version: message.version, 242 | time: message.time.tv_sec as u64, 243 | mach_time: message.mach_time, 244 | deadline: message.deadline, 245 | process: parse_es_process(process), 246 | seq_num: message.seq_num, 247 | action: match parse_es_action(message.action, &action_type) { 248 | Some(x) => x, 249 | None => return Err("Couldn't parse the action field"), 250 | }, 251 | action_type: action_type, 252 | event: event, 253 | raw_message: message, 254 | }) 255 | } 256 | 257 | /// Take in an es_process_t from the EndpointSecurity Framework, 258 | /// and parse it into a safe Rust structure. 259 | fn parse_es_process(process: &es_process_t) -> EsProcess { 260 | EsProcess { 261 | ppid: process.ppid as u32, 262 | original_ppid: process.original_ppid as u32, 263 | pid: unsafe { audit_token_to_pid(process.audit_token) as u32 }, 264 | group_id: process.group_id as u32, 265 | session_id: process.session_id as u32, 266 | codesigning_flags: process.codesigning_flags as u32, 267 | is_platform_binary: process.is_platform_binary, 268 | is_es_client: process.is_es_client, 269 | cdhash: { 270 | let mut x = String::new(); 271 | x.reserve(40); 272 | for byte in &process.cdhash { 273 | x.push_str(format!("{:02X}", byte).as_str()); 274 | } 275 | x 276 | }, 277 | signing_id: parse_es_string_token(process.signing_id), 278 | team_id: parse_es_string_token(process.team_id), 279 | executable: parse_es_file(process.executable), 280 | } 281 | } 282 | 283 | /// Take in an es_string_token from the EndpointSecurity Framework, 284 | /// and parse it into a safe Rust structure. 285 | fn parse_es_string_token(string_token: es_string_token_t) -> String { 286 | match string_token.length { 287 | x if x <= 0 => { 288 | String::new() 289 | }, 290 | _ => { 291 | match unsafe { CStr::from_ptr(string_token.data).to_str() }{ 292 | Ok(v) => v.to_owned(), 293 | Err(e) => { 294 | error!("String would not parse: {}", e); 295 | String::new() 296 | } 297 | } 298 | }, 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Allow here to prevent compiler errors from the bindgen structs and functions we can't control 2 | #![allow(non_upper_case_globals)] 3 | #![allow(non_camel_case_types)] 4 | #![allow(non_snake_case)] 5 | 6 | include!("./eps_bindings.rs"); 7 | 8 | mod parsers; 9 | 10 | extern crate libc; 11 | #[macro_use] extern crate log; 12 | 13 | use std::collections::HashSet; 14 | use std::fmt; 15 | use crossbeam_channel::Sender; 16 | use std::sync::{Arc, Mutex}; 17 | 18 | use serde::{Deserialize, Serialize}; 19 | 20 | use parsers::*; 21 | 22 | use block::*; 23 | 24 | // Values 25 | use { 26 | es_clear_cache_result_t_ES_CLEAR_CACHE_RESULT_SUCCESS as ES_CLEAR_CACHE_RESULT_SUCCESS, 27 | es_clear_cache_result_t_ES_CLEAR_CACHE_RESULT_ERR_INTERNAL as ES_CLEAR_CACHE_RESULT_ERR_INTERNAL, 28 | es_clear_cache_result_t_ES_CLEAR_CACHE_RESULT_ERR_THROTTLE as ES_CLEAR_CACHE_RESULT_ERR_THROTTLE, 29 | 30 | es_return_t_ES_RETURN_SUCCESS as ES_RETURN_SUCCESS, 31 | 32 | es_auth_result_t_ES_AUTH_RESULT_ALLOW as ES_AUTH_RESULT_ALLOW, 33 | es_auth_result_t_ES_AUTH_RESULT_DENY as ES_AUTH_RESULT_DENY, 34 | 35 | es_new_client_result_t_ES_NEW_CLIENT_RESULT_SUCCESS as ES_NEW_CLIENT_SUCCESS, 36 | es_new_client_result_t_ES_NEW_CLIENT_RESULT_ERR_INVALID_ARGUMENT as ES_NEW_CLIENT_ERROR_INVALID_ARGUMENT, 37 | es_new_client_result_t_ES_NEW_CLIENT_RESULT_ERR_INTERNAL as ES_NEW_CLIENT_ERROR_INTERNAL, 38 | es_new_client_result_t_ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED as ES_NEW_CLIENT_ERROR_NOT_ENTITLED, 39 | es_new_client_result_t_ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED as ES_NEW_CLIENT_ERROR_NOT_PERMITTED, 40 | es_new_client_result_t_ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED as ES_NEW_CLIENT_ERROR_NOT_PRIVILEGED, 41 | es_new_client_result_t_ES_NEW_CLIENT_RESULT_ERR_TOO_MANY_CLIENTS as ES_NEW_CLIENT_ERROR_TOO_MANY_CLIENTS, 42 | 43 | es_respond_result_t_ES_RESPOND_RESULT_SUCCESS as ES_RESPOND_RESULT_SUCCESS, 44 | es_respond_result_t_ES_RESPOND_RESULT_ERR_INVALID_ARGUMENT as ES_RESPONSE_RESULT_ERROR_INVALID_ARGUMENT, 45 | es_respond_result_t_ES_RESPOND_RESULT_ERR_INTERNAL as ES_RESPOND_RESULT_ERROR_INTERNAL, 46 | es_respond_result_t_ES_RESPOND_RESULT_NOT_FOUND as ES_RESPOND_RESULT_NOT_FOUND, 47 | es_respond_result_t_ES_RESPOND_RESULT_ERR_DUPLICATE_RESPONSE as ES_RESPOND_RESULT_ERROR_DUPLICATE_RESPONSE, 48 | es_respond_result_t_ES_RESPOND_RESULT_ERR_EVENT_TYPE as ES_RESPONSE_RESULT_ERROR_EVENT_TYPE, 49 | }; 50 | 51 | #[repr(C)] 52 | #[derive(Debug, PartialEq)] 53 | pub enum FileModes { 54 | Read = 0x00000001, 55 | Write = 0x00000002, 56 | NonBlock = 0x00000004, 57 | Append = 0x00000008, 58 | } 59 | 60 | #[derive(Clone, Debug, Serialize, Deserialize)] 61 | pub struct EsFile { 62 | pub path: String, 63 | pub path_truncated: bool, 64 | // pub stat: stat, 65 | } 66 | 67 | #[derive(Clone, Debug, Serialize, Deserialize)] 68 | pub struct EsProcess { 69 | //pub audit_token: rust_audit_token, 70 | pub ppid: u32, 71 | pub original_ppid: u32, 72 | pub pid: u32, 73 | pub group_id: u32, 74 | pub session_id: u32, 75 | pub codesigning_flags: u32, 76 | pub is_platform_binary: bool, 77 | pub is_es_client: bool, 78 | pub cdhash: String, 79 | pub signing_id: String, 80 | pub team_id: String, 81 | pub executable: EsFile, 82 | //pub tty: EsFile, 83 | //pub start_time: timeval, 84 | } 85 | 86 | #[derive(Clone, Debug, Serialize, Deserialize)] 87 | pub struct EsEventExec { 88 | pub target: EsProcess, 89 | pub args: Vec, 90 | // __bindgen_anon_1: es_event_exec_t__bindgen_ty_1, 91 | } 92 | 93 | #[derive(Clone, Debug, Serialize, Deserialize)] 94 | pub struct EsEventFork { 95 | pub child: EsProcess, 96 | } 97 | 98 | #[derive(Clone, Debug, Serialize, Deserialize)] 99 | pub struct EsEventClose { 100 | pub modified: bool, 101 | pub target: EsFile, 102 | } 103 | 104 | #[derive(Clone, Debug, Serialize, Deserialize)] 105 | pub struct EsEventOpen { 106 | pub fflag: u32, 107 | pub file: EsFile, 108 | // reserved: [u8; 64usize], 109 | } 110 | 111 | #[derive(Clone, Debug, Serialize, Deserialize)] 112 | pub struct EsEventKextload { 113 | pub identifier: String, 114 | // reserved: [u8; 64usize], 115 | } 116 | 117 | #[derive(Clone, Debug, Serialize, Deserialize)] 118 | pub struct EsEventKextunload { 119 | pub identifier: String, 120 | // reserved: [u8; 64usize], 121 | } 122 | 123 | #[derive(Clone, Debug, Serialize, Deserialize)] 124 | pub struct EsEventSignal { 125 | pub signal: i32, 126 | pub target: EsProcess, 127 | //pub reserved: [u8; 64usize], 128 | } 129 | 130 | #[derive(Clone, Debug, Serialize, Deserialize)] 131 | pub struct EsEventLink { 132 | pub source: EsFile, 133 | pub target_dir: EsFile, 134 | pub target_filename: String, 135 | //pub reserved: [u8; 64usize], 136 | } 137 | 138 | #[derive(Clone, Debug, Serialize, Deserialize)] 139 | pub struct EsEventUnlink { 140 | pub target: EsFile, 141 | pub parent_dir: EsFile, 142 | //pub reserved: [u8; 64usize], 143 | } 144 | 145 | #[derive(Clone, Debug, Serialize, Deserialize)] 146 | pub enum EsDestinationType { 147 | ExistingFile = 0, 148 | NewPath = 1, 149 | Unknown = 2, 150 | } 151 | 152 | #[derive(Clone, Debug, Serialize, Deserialize)] 153 | pub struct EsRenameDestinationNewPath { 154 | pub dir: EsFile, 155 | pub filename: String, 156 | } 157 | 158 | #[derive(Clone, Debug, Serialize, Deserialize)] 159 | pub struct EsRenameDestination { 160 | pub existing_file: EsFile, 161 | pub new_path: EsRenameDestinationNewPath, 162 | } 163 | 164 | #[derive(Clone, Debug, Serialize, Deserialize)] 165 | pub struct EsEventRename { 166 | pub source: EsFile, 167 | pub destination_type: EsDestinationType, 168 | pub destination: EsRenameDestination, 169 | //pub reserved: [u8; 64usize], 170 | } 171 | 172 | #[derive(Clone, Debug, Serialize, Deserialize)] 173 | pub struct EsEventReadDir { 174 | pub target: EsFile, 175 | } 176 | 177 | #[derive(Debug, Serialize, Deserialize)] 178 | pub enum EsRespondResult { 179 | Sucess, 180 | ErrorInvalidArgument, 181 | ErrorInternal, 182 | NotFound, 183 | ErrorDuplicateResponse, 184 | ErrorEventType, 185 | UnknownResponse, 186 | } 187 | 188 | #[derive(Clone, Debug, Serialize, Deserialize)] 189 | pub enum EsNewClientResult { 190 | Success, 191 | ErrorInvalidArgument, 192 | ErrorInternal, 193 | ErrorNotEntitled, 194 | ErrorNotPermitted, 195 | ErrorNotPrivileged, 196 | ErrorTooManyClients, 197 | Unknown, 198 | } 199 | 200 | #[derive(Debug, Serialize, Deserialize)] 201 | pub enum ClearCacheResult { 202 | Success, 203 | ErrorInternal, 204 | ErrorThrottle, 205 | } 206 | 207 | #[derive(Clone, Debug, Serialize, Deserialize)] 208 | pub struct EsClientError { 209 | pub details: String, 210 | pub error_type: EsNewClientResult, 211 | } 212 | 213 | impl fmt::Display for EsClientError { 214 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 215 | write!(f,"{}", self.details) 216 | } 217 | } 218 | 219 | #[repr(C)] 220 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 221 | pub enum SupportedEsEvent { 222 | AuthExec = 0, 223 | AuthOpen = 1, 224 | AuthKextload = 2, 225 | AuthRename = 6, 226 | AuthSignal = 7, 227 | AuthUnlink = 8, 228 | NotifyExec = 9, 229 | NotifyOpen = 10, 230 | NotifyFork = 11, 231 | NotifyClose = 12, 232 | NotifyKextload = 17, 233 | NotifyKextunload = 18, 234 | NotifyLink = 19, 235 | NotifyRename = 25, 236 | NotifySignal = 31, 237 | NotifyUnlink = 32, 238 | AuthLink = 42, 239 | AuthReadDir = 67, 240 | NotifyReadDir = 68, 241 | } 242 | 243 | impl fmt::Display for SupportedEsEvent { 244 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 245 | write!(f, "{:?}", self) 246 | } 247 | } 248 | 249 | #[derive(Clone, Debug, Serialize, Deserialize)] 250 | pub enum EsEvent { 251 | AuthExec(EsEventExec), 252 | AuthOpen(EsEventOpen), 253 | AuthKextload(EsEventKextload), 254 | /*AuthMmap, 255 | AuthMprotect, 256 | AuthMount,*/ 257 | AuthRename(EsEventRename), 258 | AuthSignal(EsEventSignal), 259 | AuthUnlink(EsEventUnlink), 260 | NotifyExec(EsEventExec), 261 | NotifyOpen(EsEventOpen), 262 | NotifyFork(EsEventFork), 263 | NotifyClose(EsEventClose), 264 | /*NotifyCreate, 265 | NotifyExchangedata, 266 | NotifyExit, 267 | NotifyGetTask,*/ 268 | NotifyKextload(EsEventKextload), 269 | NotifyKextunload(EsEventKextunload), 270 | NotifyLink(EsEventLink), 271 | /*NotifyMmap, 272 | NotifyMprotect, 273 | NotifyMount, 274 | NotifyUnmount, 275 | NotifyIokitOpen,*/ 276 | NotifyRename(EsEventRename), 277 | /*NotifySetattrlist, 278 | NotifySetextattr, 279 | NotifySetflags, 280 | NotifySetmode, 281 | NotifySetowner,*/ 282 | NotifySignal(EsEventSignal), 283 | NotifyUnlink(EsEventUnlink), 284 | /*NotifyWrite, 285 | AuthFileProviderMaterialize, 286 | NotifyFileProviderMaterialize, 287 | AuthFileProviderUpdate, 288 | NotifyFileProviderUpdate, 289 | AuthReadlink, 290 | NotifyReadlink, 291 | AuthTruncate, 292 | NotifyTruncate,*/ 293 | AuthLink(EsEventLink), 294 | /*NotifyLookup, 295 | AuthCreate, 296 | AuthSetattrlist, 297 | AuthSetextattr, 298 | AuthSetflags, 299 | AuthSetmode, 300 | AuthSetowner, 301 | AuthChdir, 302 | NotifyChdir, 303 | AuthGetattrlist, 304 | NotifyGetattrlist, 305 | NotifyStat, 306 | NotifyAccess, 307 | AuthChroot, 308 | NotifyChroot, 309 | AuthUtimes, 310 | NotifyUtimes, 311 | AuthClone, 312 | NotifyClone, 313 | NotifyFcntl, 314 | AuthGetextattr, 315 | NotifyGetextattr, 316 | AuthListextattr, 317 | NotifyListextattr,*/ 318 | AuthReadDir(EsEventReadDir), 319 | NotifyReadDir(EsEventReadDir), 320 | /*AuthDeleteextattr, 321 | NotifyDeleteextattr, 322 | AuthFsgetpath, 323 | NotifyFsgetpath, 324 | NotifyDup, 325 | AuthSettime, 326 | NotifySettime, 327 | NotifyUipcBind, 328 | AuthUipcBind, 329 | NotifyUipcConnect, 330 | AuthUipcConnect, 331 | AuthExchangedata, 332 | AuthSetacl, 333 | NotifySetacl, 334 | NotifyPtyGrant, 335 | NotifyPtyClose, 336 | AuthProcCheck, 337 | NotifyProcCheck, 338 | AuthGetTask, 339 | AuthSearchfs, 340 | NotifySearchfs, 341 | AuthFcntl, 342 | AuthIokitOpen, 343 | AuthProcSuspendResume, 344 | NotifyProcSuspendResume, 345 | NotifyCsInvalidated, 346 | NotifyGetTaskName, 347 | NotifyTrace, 348 | NotifyRemoteThreadCreate, 349 | AuthRemount, 350 | NotifyRemount, 351 | Last,*/ 352 | } 353 | 354 | #[derive(Clone, Debug, Serialize, Deserialize)] 355 | pub enum EsCacheResult { 356 | Yes, 357 | No, 358 | } 359 | 360 | #[derive(Clone, Debug, Serialize, Deserialize)] 361 | pub enum EsActionType { 362 | Auth, 363 | Notify, 364 | } 365 | 366 | #[derive(Clone, Debug, Serialize, Deserialize)] 367 | pub enum EsAuthResult { 368 | Allow, 369 | Deny, 370 | } 371 | 372 | #[derive(Clone, Debug, Serialize, Deserialize)] 373 | pub enum EsResultType { 374 | Auth, 375 | Flags, 376 | } 377 | 378 | #[repr(C)] 379 | #[derive(Debug, Copy, Clone, Serialize, Deserialize)] 380 | pub struct EsEventId { 381 | pub reserved: [u8; 32usize], 382 | } 383 | 384 | #[derive(Clone, Debug, Serialize, Deserialize)] 385 | pub struct EsResultNotifyResult { 386 | pub flags: u32, 387 | } 388 | 389 | #[derive(Clone, Debug, Serialize, Deserialize)] 390 | pub struct EsResult { 391 | pub result_type: EsResultType, 392 | pub result: EsResultNotifyResult, 393 | } 394 | 395 | #[derive(Clone, Debug, Serialize, Deserialize)] 396 | pub enum EsAction { 397 | Auth(EsEventId), 398 | Notify(EsResult), 399 | } 400 | 401 | // This is only needed because EsMessage contains a raw pointer 402 | // to the es_message 403 | unsafe impl Send for EsMessage {} 404 | unsafe impl Sync for EsMessage {} 405 | #[derive(Clone, Debug, Serialize)] 406 | pub struct EsMessage { 407 | pub version: u32, 408 | pub time: u64, 409 | pub mach_time: u64, 410 | pub deadline: u64, 411 | pub process: EsProcess, 412 | pub seq_num: u64, 413 | pub action: EsAction, 414 | pub action_type: EsActionType, 415 | pub event: EsEvent, 416 | #[serde(skip_serializing)] 417 | raw_message: *const es_message_t, 418 | } 419 | 420 | 421 | struct EsClientHidden { 422 | client: *mut es_client_t, 423 | active_subscriptions: HashSet, 424 | } 425 | 426 | // Unfortunately this system is a little over zealous 427 | // because it means we have to lock even to read active subscriptions. 428 | // Optimize this later if it provides too much contention with responding 429 | // to messages. 430 | #[derive(Clone)] 431 | pub struct EsClient { 432 | client: Arc>, 433 | } 434 | 435 | // TODO: Codegen these in the future 436 | // TODO: Really. Codegen these in the future along with the protobuf defintions 437 | pub fn raw_event_to_supportedesevent(event_type: u64) -> Option { 438 | Some(match event_type { 439 | 0 => SupportedEsEvent::AuthExec, 440 | 1 => SupportedEsEvent::AuthOpen, 441 | 2 => SupportedEsEvent::AuthKextload, 442 | 6 => SupportedEsEvent::AuthRename, 443 | 7 => SupportedEsEvent::AuthSignal, 444 | 8 => SupportedEsEvent::AuthUnlink, 445 | 9 => SupportedEsEvent::NotifyExec, 446 | 10 => SupportedEsEvent::NotifyOpen, 447 | 11 => SupportedEsEvent::NotifyFork, 448 | 12 => SupportedEsEvent::NotifyClose, 449 | 17 => SupportedEsEvent::NotifyKextload, 450 | 18 => SupportedEsEvent::NotifyKextunload, 451 | 19 => SupportedEsEvent::NotifyLink, 452 | 25 => SupportedEsEvent::NotifyRename, 453 | 31 => SupportedEsEvent::NotifySignal, 454 | 32 => SupportedEsEvent::NotifyUnlink, 455 | 42 => SupportedEsEvent::AuthLink, 456 | 67 => SupportedEsEvent::AuthReadDir, 457 | 68 => SupportedEsEvent::NotifyReadDir, 458 | _ => return None 459 | }) 460 | } 461 | 462 | // TODO: Really. Codegen these in the future along with the protobuf defintions 463 | pub fn supportedesevent_to_raw_event(event_type: &SupportedEsEvent) -> u32 { 464 | match event_type { 465 | SupportedEsEvent::AuthExec => 0, 466 | SupportedEsEvent::AuthOpen => 1, 467 | SupportedEsEvent::AuthKextload => 2, 468 | SupportedEsEvent::AuthRename => 6, 469 | SupportedEsEvent::AuthSignal => 7, 470 | SupportedEsEvent::AuthUnlink => 8, 471 | SupportedEsEvent::NotifyExec => 9, 472 | SupportedEsEvent::NotifyOpen => 10, 473 | SupportedEsEvent::NotifyFork => 11, 474 | SupportedEsEvent::NotifyClose => 12, 475 | SupportedEsEvent::NotifyKextload => 17, 476 | SupportedEsEvent::NotifyKextunload => 18, 477 | SupportedEsEvent::NotifyLink => 19, 478 | SupportedEsEvent::NotifyRename => 25, 479 | SupportedEsEvent::NotifySignal => 31, 480 | SupportedEsEvent::NotifyUnlink => 32, 481 | SupportedEsEvent::AuthLink => 42, 482 | SupportedEsEvent::AuthReadDir => 67, 483 | SupportedEsEvent::NotifyReadDir => 68, 484 | } 485 | } 486 | 487 | fn es_notify_callback(_client: *mut es_client_t, message: *mut es_message_t, tx: Sender) { 488 | let message = match parse_es_message(message) { 489 | Err(e) => { println!("Could not parse message: {}", e); return}, 490 | Ok(x) => x, 491 | }; 492 | 493 | match tx.send(message) { 494 | Err(e) => println!("Error logging event: {}", e), 495 | _ => (), 496 | } 497 | } 498 | 499 | pub fn create_es_client(tx: Sender) -> Result { 500 | let mut client: *mut es_client_t = std::ptr::null_mut(); 501 | let client_ptr: *mut *mut es_client_t = &mut client; 502 | 503 | let handler = ConcreteBlock::new(move |client: *mut es_client_t, message: *mut es_message_t| { 504 | es_notify_callback(client, message, tx.clone()); 505 | }).copy(); 506 | 507 | match unsafe { es_new_client(client_ptr, &*handler as *const Block<_, _> as *const std::ffi::c_void) } { 508 | ES_NEW_CLIENT_SUCCESS => { 509 | let hidden = EsClientHidden { 510 | client: client, 511 | active_subscriptions: HashSet::new(), 512 | }; 513 | Ok(EsClient { 514 | client: Arc::new(Mutex::new(hidden)), 515 | } 516 | )}, 517 | ES_NEW_CLIENT_ERROR_INVALID_ARGUMENT => Err(EsClientError{ 518 | details: String::from("Something incorrect was passed to es_new_client"), 519 | error_type: EsNewClientResult::ErrorInvalidArgument, 520 | }), 521 | ES_NEW_CLIENT_ERROR_INTERNAL => Err(EsClientError{ 522 | details: String::from("es_new_client experienced an internal error"), 523 | error_type: EsNewClientResult::ErrorInternal, 524 | }), 525 | ES_NEW_CLIENT_ERROR_NOT_ENTITLED => Err(EsClientError{ 526 | details: String::from("This process doesn't have the EndpointSecurity entitlement. (Is the binary signed correctly, is there a provisioning profile installed to allow your program to access EPS?)"), 527 | error_type: EsNewClientResult::ErrorNotEntitled, 528 | }), 529 | ES_NEW_CLIENT_ERROR_NOT_PERMITTED => Err(EsClientError{ 530 | details: String::from("This process is not permitted to use the EndpointSecurity Framework. (Do you have Full Disk Access for your process?)"), 531 | error_type: EsNewClientResult::ErrorNotPermitted, 532 | }), 533 | ES_NEW_CLIENT_ERROR_NOT_PRIVILEGED => Err(EsClientError{ 534 | details: String::from("The process must be running as root to access the EndpointSecurity Framework"), 535 | error_type: EsNewClientResult::ErrorNotPrivileged, 536 | }), 537 | ES_NEW_CLIENT_ERROR_TOO_MANY_CLIENTS => Err(EsClientError{ 538 | details: String::from("There are too many clients connected to EndpointSecurit"), 539 | error_type: EsNewClientResult::ErrorTooManyClients, 540 | }), 541 | _ => Err(EsClientError{ 542 | details: String::from("es_new_client returned an unknown error"), 543 | error_type: EsNewClientResult::Unknown, 544 | }), 545 | } 546 | } 547 | 548 | // This might not be true. I'm talking with Apple to figure it out but nothing 549 | // seems to have broken with it yet. 550 | // @obelisk Investigate more 551 | unsafe impl Send for EsClient {} 552 | unsafe impl Sync for EsClient {} 553 | 554 | impl EsClient { 555 | // Clear the cache of decisions. This should be done sparringly as it affects ALL 556 | // client for the entire system. Doing this too frequently will impact system performace 557 | pub fn clear_cache(&self) -> Result <(), ClearCacheResult> { 558 | let client = (*self.client).lock(); 559 | let client = match client { 560 | Ok(c) => c, 561 | Err(e) => { 562 | error!("Could not acquire lock for client: {}", e); 563 | return Err(ClearCacheResult::ErrorInternal) 564 | }, 565 | }; 566 | 567 | let response = unsafe { 568 | es_clear_cache(client.client) 569 | }; 570 | 571 | match response { 572 | ES_CLEAR_CACHE_RESULT_SUCCESS => Ok(()), 573 | ES_CLEAR_CACHE_RESULT_ERR_INTERNAL => Err(ClearCacheResult::ErrorInternal), 574 | ES_CLEAR_CACHE_RESULT_ERR_THROTTLE => Err(ClearCacheResult::ErrorThrottle), 575 | _ => { 576 | error!("Unknown response from es_clear_cache. Perhaps the library needs updating?"); 577 | Err(ClearCacheResult::ErrorInternal) 578 | } 579 | } 580 | } 581 | 582 | // Start receiving callbacks for specified events 583 | pub fn subscribe_to_events(&self, events: &Vec) -> bool { 584 | if events.len() > 128 { 585 | println!("Too many events to subscribe to!"); 586 | return false; 587 | } 588 | 589 | let client = (*self.client).lock(); 590 | let mut client = match client { 591 | Ok(c) => c, 592 | Err(_) => return false, 593 | }; 594 | 595 | let events:Vec<&SupportedEsEvent> = events.iter().filter(|x| !client.active_subscriptions.contains(x)).collect(); 596 | if events.len() == 0 { 597 | debug!(target: "endpointsecurity-rs", "No new events being subscribed to"); 598 | return true; 599 | } 600 | 601 | let mut c_events: [u32; 128] = [0; 128]; 602 | let mut i = 0; 603 | for event in &events { 604 | c_events[i] = supportedesevent_to_raw_event(&*event); 605 | i += 1; 606 | } 607 | 608 | unsafe { 609 | match es_subscribe(client.client, &c_events as *const u32, events.len() as u32) { 610 | ES_RETURN_SUCCESS => { 611 | for event in events { 612 | client.active_subscriptions.insert(*event); 613 | } 614 | true 615 | }, 616 | _ => false, 617 | } 618 | } 619 | } 620 | 621 | // Unsubscribe from events and stop receiving callbacks for them 622 | pub fn unsubscribe_to_events(&self, events: &Vec) -> bool { 623 | if events.len() > 128 { 624 | println!("Too many events to unsubscribe to!"); 625 | return false; 626 | } 627 | 628 | let client = (*self.client).lock(); 629 | let mut client = match client { 630 | Ok(c) => c, 631 | Err(_) => return false, 632 | }; 633 | 634 | let events:Vec<&SupportedEsEvent> = events.iter().filter(|x| client.active_subscriptions.contains(x)).collect(); 635 | if events.len() == 0 { 636 | debug!(target: "endpointsecurity-rs", "Not subscribed to any events request to unsubscribe from"); 637 | return true; 638 | } 639 | 640 | let mut c_events: [u32; 128] = [0; 128]; 641 | let mut i = 0; 642 | for event in &events { 643 | c_events[i] = supportedesevent_to_raw_event(&*event); 644 | i += 1; 645 | } 646 | 647 | unsafe { 648 | match es_unsubscribe(client.client, &c_events as *const u32, events.len() as u32) { 649 | ES_RETURN_SUCCESS => { 650 | for event in events { 651 | client.active_subscriptions.remove(event); 652 | } 653 | true 654 | }, 655 | _ => false, 656 | } 657 | } 658 | } 659 | 660 | // Set your subscriptions to these regardless of what they were before 661 | pub fn set_subscriptions_to(&self, events: &Vec) -> bool { 662 | if events.len() > 128 { 663 | println!("Too many events to unsubscribe to!"); 664 | return false; 665 | } 666 | let new_subscriptions:Vec; 667 | let remove_subscriptions:Vec; 668 | 669 | { 670 | let client = (*self.client).lock(); 671 | let client = match client { 672 | Ok(c) => c, 673 | Err(_) => return false, 674 | }; 675 | 676 | // Filter out all subscriptions that we already have 677 | new_subscriptions = events.iter().filter(|x| !client.active_subscriptions.contains(x)).copied().collect(); 678 | 679 | // For all subscriptions we have, keep them in this remove list if they are not in our new list 680 | remove_subscriptions = client.active_subscriptions.iter().filter(|x| !events.contains(x)).copied().collect(); 681 | if !new_subscriptions.is_empty() { 682 | info!(target: "endpointsecurity-rs", "Adding subscriptions for: {}", 683 | new_subscriptions.iter().fold(String::from(""), |acc, x| acc + &x.to_string() + ", ")); 684 | } 685 | 686 | if !remove_subscriptions.is_empty() { 687 | info!(target: "endpointsecurity-rs", "Removing subscriptions for: {}", 688 | remove_subscriptions.iter().fold(String::from(""), |acc, x| acc + &x.to_string() + ", ")); 689 | } 690 | } 691 | self.unsubscribe_to_events(&remove_subscriptions) && self.subscribe_to_events(&new_subscriptions) 692 | } 693 | 694 | pub fn respond_to_flags_event(&self, message: &EsMessage, authorized_flags: u32, should_cache: &EsCacheResult) -> EsRespondResult { 695 | let cache = match should_cache { 696 | EsCacheResult::Yes => true, 697 | EsCacheResult::No => false, 698 | }; 699 | 700 | let client = (*self.client).lock(); 701 | let client = match client { 702 | Ok(c) => c, 703 | Err(_) => return EsRespondResult::UnknownResponse, // TODO Fix this 704 | }; 705 | 706 | match unsafe { es_respond_flags_result(client.client, message.raw_message, authorized_flags, cache) } { 707 | ES_RESPOND_RESULT_SUCCESS => EsRespondResult::Sucess, 708 | ES_RESPONSE_RESULT_ERROR_INVALID_ARGUMENT => EsRespondResult::ErrorInvalidArgument, 709 | ES_RESPOND_RESULT_ERROR_INTERNAL => EsRespondResult::ErrorInternal, 710 | ES_RESPOND_RESULT_NOT_FOUND => EsRespondResult::NotFound, 711 | ES_RESPOND_RESULT_ERROR_DUPLICATE_RESPONSE => EsRespondResult::ErrorDuplicateResponse, 712 | ES_RESPONSE_RESULT_ERROR_EVENT_TYPE => EsRespondResult::ErrorEventType, 713 | _ => EsRespondResult::UnknownResponse, 714 | } 715 | } 716 | 717 | pub fn respond_to_auth_event(&self, message: &EsMessage, response: &EsAuthResult, should_cache: &EsCacheResult) -> EsRespondResult { 718 | let cache = match should_cache { 719 | EsCacheResult::Yes => true, 720 | EsCacheResult::No => false, 721 | }; 722 | 723 | let response = match response { 724 | EsAuthResult::Allow => ES_AUTH_RESULT_ALLOW, 725 | EsAuthResult::Deny => ES_AUTH_RESULT_DENY, 726 | }; 727 | 728 | let client = (*self.client).lock(); 729 | let client = match client { 730 | Ok(c) => c, 731 | Err(_) => return EsRespondResult::UnknownResponse, // TODO Fix this 732 | }; 733 | 734 | match unsafe { es_respond_auth_result(client.client, message.raw_message, response, cache) } { 735 | ES_RESPOND_RESULT_SUCCESS => EsRespondResult::Sucess, 736 | ES_RESPONSE_RESULT_ERROR_INVALID_ARGUMENT => EsRespondResult::ErrorInvalidArgument, 737 | ES_RESPOND_RESULT_ERROR_INTERNAL => EsRespondResult::ErrorInternal, 738 | ES_RESPOND_RESULT_NOT_FOUND => EsRespondResult::NotFound, 739 | ES_RESPOND_RESULT_ERROR_DUPLICATE_RESPONSE => EsRespondResult::ErrorDuplicateResponse, 740 | ES_RESPONSE_RESULT_ERROR_EVENT_TYPE => EsRespondResult::ErrorEventType, 741 | _ => EsRespondResult::UnknownResponse, 742 | } 743 | } 744 | } 745 | 746 | impl Drop for EsClient { 747 | fn drop(&mut self) { 748 | unsafe { 749 | let client = (*self.client).lock(); 750 | let mut client = match client { 751 | Ok(c) => c, 752 | Err(_) => return (), 753 | }; 754 | 755 | es_delete_client(client.client); 756 | // Probably unnecessary 757 | client.active_subscriptions.clear(); 758 | } 759 | } 760 | } --------------------------------------------------------------------------------