// // FileMonitor.m // FileMonitor // // Created by Patrick Wardle on 9/1/19. // Copyright © 2019 Objective-See. All rights reserved. // // Inspired by https://gist.github.com/Omar-Ikram/8e6721d8e83a3da69b31d4c2612a68ba // NOTE: requires a) root b) the 'com.apple.developer.endpoint-security.client' entitlement #import "utilities.h" #import "FileMonitor.h" #import #import //endpoint es_client_t *endpointClient = nil; //file events of interest es_event_type_t events[] = {ES_EVENT_TYPE_NOTIFY_CREATE, ES_EVENT_TYPE_NOTIFY_OPEN, ES_EVENT_TYPE_NOTIFY_WRITE, ES_EVENT_TYPE_NOTIFY_CLOSE, ES_EVENT_TYPE_NOTIFY_RENAME, ES_EVENT_TYPE_NOTIFY_LINK, ES_EVENT_TYPE_NOTIFY_UNLINK}; @implementation FileMonitor //start monitoring -(BOOL)start:(FileCallbackBlock)callback { //flag BOOL started = NO; //result es_new_client_result_t result = 0; //sync @synchronized (self) { //create client // callback invoked on file events result = es_new_client(&endpointClient, ^(es_client_t *client, const es_message_t *message) { //new file obj File* file = nil; //init file obj file = [[File alloc] init:(es_message_t* _Nonnull)message]; if(nil != file) { //invoke user callback callback(file); } }); //error? if(ES_NEW_CLIENT_RESULT_SUCCESS != result) { //err msg NSLog(@"ERROR: es_new_client() failed"); //provide more info switch (result) { //not entitled case ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED: NSLog(@"ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED: \"The caller is not properly entitled to connect\""); break; //not permitted case ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED: NSLog(@"ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED: \"The caller is not permitted to connect. They lack Transparency, Consent, and Control (TCC) approval form the user.\""); break; //not privileged case ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED: NSLog(@"ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED: \"The caller is not running as root\""); break; default: break; } //bail goto bail; } //clear cache if(ES_CLEAR_CACHE_RESULT_SUCCESS != es_clear_cache(endpointClient)) { //err msg NSLog(@"ERROR: es_clear_cache() failed"); //bail goto bail; } //mute self // note: you might not want this, but for a cmdline-based filemonitor // this ensures we don't constantly report writes to current /dev/tty es_mute_path_literal(endpointClient, [NSProcessInfo.processInfo.arguments[0] UTF8String]); //subscribe if(ES_RETURN_SUCCESS != es_subscribe(endpointClient, events, sizeof(events)/sizeof(events[0]))) { //err msg NSLog(@"ERROR: es_subscribe() failed"); //bail goto bail; } } //sync //happy started = YES; bail: return started; } //stop -(BOOL)stop { //flag BOOL stopped = NO; //sync @synchronized (self) { //unsubscribe & delete if(NULL != endpointClient) { //unsubscribe if(ES_RETURN_SUCCESS != es_unsubscribe_all(endpointClient)) { //err msg NSLog(@"ERROR: es_unsubscribe_all() failed"); //bail goto bail; } //delete if(ES_RETURN_SUCCESS != es_delete_client(endpointClient)) { //err msg NSLog(@"ERROR: es_delete_client() failed"); //bail goto bail; } //unset endpointClient = NULL; //happy stopped = YES; } } //sync bail: return stopped; } @end