diff --git a/Installer/Configure.h b/Installer/Configure.h index da37ba7..44fb103 100644 --- a/Installer/Configure.h +++ b/Installer/Configure.h @@ -29,7 +29,7 @@ -(BOOL)install; //uninstall --(BOOL)uninstall; +-(BOOL)uninstall:(NSUInteger)type; @end diff --git a/Installer/Configure.m b/Installer/Configure.m index 7ad17fb..35253f8 100644 --- a/Installer/Configure.m +++ b/Installer/Configure.m @@ -41,7 +41,8 @@ [self stop]; //uninstall - if(YES != [self uninstall]) + // ->but do partial (leave whitelist) + if(YES != [self uninstall:UNINSTALL_PARIAL]) { //bail goto bail; @@ -89,7 +90,7 @@ logMsg(LOG_DEBUG, @"uninstalling..."); //uninstall - if(YES != [self uninstall]) + if(YES != [self uninstall:UNINSTALL_FULL]) { //bail goto bail; @@ -185,7 +186,7 @@ bail: //call into login item to install itself // ->runs as logged in user, so can access user's login items, etc - execTask(SUDO, @[@"-u", user, loginItem, ACTION_INSTALL]); + execTask(SUDO, @[@"-u", user, loginItem, [NSString stringWithUTF8String:CMD_INSTALL]]); //dbg msg logMsg(LOG_DEBUG, [NSString stringWithFormat:@"persisted %@", loginItem]); @@ -277,7 +278,7 @@ bail: //uninstall // ->delete app --(BOOL)uninstall +-(BOOL)uninstall:(NSUInteger)type { //return/status var BOOL wasUninstalled = NO; @@ -286,23 +287,26 @@ bail: // ->since want to try all uninstall steps, but record if any fail BOOL bAnyErrors = NO; - //path to finder sync - NSString* appPath = nil; + //path to login item + NSString* loginItem = nil; + + //path to installed app + NSString* installedAppPath = nil; //error NSError* error = nil; - //path to login item - NSString* loginItem = nil; - //logged in user NSString* user = nil; - - //init path - appPath = [APPS_FOLDER stringByAppendingPathComponent:APP_NAME]; //init path to login item - loginItem = [appPath stringByAppendingPathComponent:@"Contents/Library/LoginItems/OverSight Helper.app/Contents/MacOS/OverSight Helper"]; + loginItem = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:APP_NAME] stringByAppendingPathComponent:@"Contents/Library/LoginItems/OverSight Helper.app/Contents/MacOS/OverSight Helper"]; + + //init path to installed app + installedAppPath = [APPS_FOLDER stringByAppendingPathComponent:APP_NAME]; + + //dbg msg + logMsg(LOG_DEBUG, @"uninstalling login item"); //get user user = loggedinUser(); @@ -315,25 +319,30 @@ bail: bAnyErrors = YES; //keep uninstalling... - } //unistall login item else { + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"telling login item %@, to uninstall itself", loginItem]); + //call into login item to uninstall itself // ->runs as logged in user, so can access user's login items, etc - execTask(SUDO, @[@"-u", user, loginItem, ACTION_UNINSTALL]); + execTask(SUDO, @[@"-u", user, loginItem, [NSString stringWithUTF8String:CMD_UNINSTALL]]); //dbg msg logMsg(LOG_DEBUG, [NSString stringWithFormat:@"unpersisted %@", loginItem]); } + + //dbg msg + logMsg(LOG_DEBUG, @"deleting app"); //delete folder - if(YES != [[NSFileManager defaultManager] removeItemAtPath:appPath error:&error]) + if(YES != [[NSFileManager defaultManager] removeItemAtPath:installedAppPath error:&error]) { //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to delete app %@ (%@)", appPath, error]); + logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to delete app %@ (%@)", installedAppPath, error]); //set flag bAnyErrors = YES; @@ -341,6 +350,30 @@ bail: //keep uninstalling... } + //full uninstall? + // ->remove app support directory too + if(UNINSTALL_FULL == type) + { + //dbg msg + logMsg(LOG_DEBUG, @"full, so also deleting app support directory"); + + //delete app support folder + if(YES == [[NSFileManager defaultManager] fileExistsAtPath:[APP_SUPPORT_DIRECTORY stringByExpandingTildeInPath]]) + { + //delete + if(YES != [self removeAppSupport]) + { + //err msg + logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to delete app support directory %@", APP_SUPPORT_DIRECTORY]); + + //set flag + bAnyErrors = YES; + + //keep uninstalling... + } + } + } + //only success when there were no errors if(YES != bAnyErrors) { @@ -351,6 +384,49 @@ bail: return wasUninstalled; } +//remove ~/Library/Application Support/Objective-See/OverSight +// and also ~/Library/Application Support/Objective-See/ if nothing else is in there (no other products) +-(BOOL)removeAppSupport +{ + //flag + BOOL removedDirectory = NO; + + //error + NSError* error = nil; + + //delete OverSight directory + if(YES != [[NSFileManager defaultManager] removeItemAtPath:[APP_SUPPORT_DIRECTORY stringByExpandingTildeInPath] error:&error]) + { + //err msg + logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to delete OverSight's app support directory %@ (%@)", APP_SUPPORT_DIRECTORY, error]); + + //bail + goto bail; + } + + //anything left in ~/Library/Application Support/Objective-See/? + // ->nope: delete it + if(0 == [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:[[APP_SUPPORT_DIRECTORY stringByExpandingTildeInPath] stringByDeletingLastPathComponent] error:nil] count]) + { + if(YES != [[NSFileManager defaultManager] removeItemAtPath:[[APP_SUPPORT_DIRECTORY stringByExpandingTildeInPath] stringByDeletingLastPathComponent] error:&error]) + { + //err msg + logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to delete Objective-See's app support directory %@ (%@)", [APP_SUPPORT_DIRECTORY stringByDeletingLastPathComponent], error]); + + //bail + goto bail; + } + } + + //happy + removedDirectory = YES; + +//bail +bail: + + return removedDirectory; +} + @end diff --git a/Installer/ConfigureWindowController.m b/Installer/ConfigureWindowController.m index 7079f2f..8602469 100644 --- a/Installer/ConfigureWindowController.m +++ b/Installer/ConfigureWindowController.m @@ -7,6 +7,7 @@ // #import "Consts.h" +#import "Logging.h" #import "Configure.h" #import "Utilities.h" #import "ConfigureWindowController.h" @@ -108,47 +109,60 @@ //action NSUInteger action = 0; - //hide 'get more info' button - self.moreInfoButton.hidden = YES; + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"handling action click: %@", buttonTitle]); - //set action - // ->install daemon - if(YES == [buttonTitle isEqualToString:ACTION_INSTALL]) + //close? + // ->just exit + if(YES == [buttonTitle isEqualToString:ACTION_CLOSE]) { - //set - action = ACTION_INSTALL_FLAG; + //close + [self.window close]; } - //set action - // ->uninstall daemon + + //install/uninstall logic handlers else { - //set - action = ACTION_UNINSTALL_FLAG; + + //hide 'get more info' button + self.moreInfoButton.hidden = YES; + + //set action + // ->install daemon + if(YES == [buttonTitle isEqualToString:ACTION_INSTALL]) + { + //set + action = ACTION_INSTALL_FLAG; + } + //set action + // ->uninstall daemon + else + { + //set + action = ACTION_UNINSTALL_FLAG; + } + + //disable 'x' button + // ->don't want user killing app during install/upgrade + [[self.window standardWindowButton:NSWindowCloseButton] setEnabled:NO]; + + //clear status msg + [self.statusMsg setStringValue:@""]; + + //force redraw of status msg + // ->sometime doesn't refresh (e.g. slow VM) + [self.statusMsg setNeedsDisplay:YES]; + + //invoke logic to install/uninstall + // ->do in background so UI doesn't block + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + ^{ + //install/uninstall + [self lifeCycleEvent:action]; + + }); } - - //disable 'x' button - // ->don't want user killing app during install/upgrade - [[self.window standardWindowButton:NSWindowCloseButton] setEnabled:NO]; - - //clear status msg - [self.statusMsg setStringValue:@""]; - - //force redraw of status msg - // ->sometime doesn't refresh (e.g. slow VM) - [self.statusMsg setNeedsDisplay:YES]; - - //invoke logic to install/uninstall - // ->do in background so UI doesn't block - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - ^{ - //install/uninstall - [self lifeCycleEvent:action]; - - }); -//bail -bail: - return; } @@ -195,7 +209,7 @@ bail: //sleep // ->allow 'install' || 'uninstall' msg to show up - sleep(1); + [NSThread sleepForTimeInterval:1.0f]; //perform action (install | uninstall) // ->perform background actions @@ -336,27 +350,34 @@ bail: //set status msg [self.statusMsg setStringValue:resultMsg]; - //toggle buttons - // ->after install turn on 'uninstall' and off 'install' + //update button + // ->after install change butter to 'close' if(ACTION_INSTALL_FLAG == event) { - //enable uninstall - self.uninstallButton.enabled = YES; + //set button title to 'close' + self.installButton.title = ACTION_CLOSE; - //disable install - self.installButton.enabled = NO; + //enable + self.installButton.enabled = YES; + + //make it active + [self.window makeFirstResponder:self.installButton]; } - //toggle buttons - // ->after uninstall turn off 'uninstall' and on 'install' + //update button + // ->after uninstall change butter to 'close' else { - //disable - self.uninstallButton.enabled = NO; + //set button title to 'close' + self.uninstallButton.title = ACTION_CLOSE; - //enable close button - self.installButton.enabled = YES; + //enable + self.uninstallButton.enabled = YES; + + //make it active + [self.window makeFirstResponder:self.uninstallButton]; } + //ok to re-enable 'x' button [[self.window standardWindowButton:NSWindowCloseButton] setEnabled:YES]; diff --git a/Installer/Info.plist b/Installer/Info.plist index cd8dc2a..679eee6 100644 --- a/Installer/Info.plist +++ b/Installer/Info.plist @@ -17,15 +17,15 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.0 + 1.1.0 CFBundleSignature ???? CFBundleVersion - 1.0.0 + 1.1.0 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright - Copyright (c) 2016 Objective-See. All rights reserved. + Copyright (c) 2017 Objective-See. All rights reserved. NSMainNibFile MainMenu NSPrincipalClass diff --git a/Installer/main.m b/Installer/main.m index 14ee93f..4bcd5d6 100644 --- a/Installer/main.m +++ b/Installer/main.m @@ -11,6 +11,8 @@ #import "Configure.h" #import +//TODO: wrap debug msgs! + int main(int argc, const char * argv[]) { //return var diff --git a/LoginItem/AVMonitor.h b/LoginItem/AVMonitor.h index a7dece3..bfa7388 100644 --- a/LoginItem/AVMonitor.h +++ b/LoginItem/AVMonitor.h @@ -43,6 +43,12 @@ //flag indicating video (camera) is active @property BOOL videoActive; +//flag indicating an audio active alert was shown +@property BOOL showAudioDeactivation; + +//flag indicating a video active alert was shown +@property BOOL showVideoDeactivation; + //monitor thread @property(nonatomic, retain)NSThread* videoMonitorThread; @@ -55,10 +61,16 @@ //last notification @property(nonatomic, retain)NSString* lastNotification; +//whitelisted procs +@property(nonatomic, retain)NSMutableArray* whiteList; + /* METHODS */ +//load whitelist +-(void)loadWhitelist; + //kicks off thread to monitor -(BOOL)monitor; diff --git a/LoginItem/AVMonitor.m b/LoginItem/AVMonitor.m index a97064b..80b6497 100644 --- a/LoginItem/AVMonitor.m +++ b/LoginItem/AVMonitor.m @@ -19,12 +19,16 @@ @synthesize mic; @synthesize camera; @synthesize lastEvent; +@synthesize whiteList; @synthesize audioActive; -@synthesize videoActive; @synthesize lastNotification; @synthesize videoMonitorThread; +@synthesize showAudioDeactivation; +@synthesize showVideoDeactivation; @synthesize rememberWindowController; +//TODO: fix hang!! + //init -(id)init { @@ -32,12 +36,48 @@ self = [super init]; if(nil != self) { - + //load whitelist + [self loadWhitelist]; } return self; } +//load whitelist +-(void)loadWhitelist +{ + //path + NSString* path = nil; + + //init path + path = [[APP_SUPPORT_DIRECTORY stringByExpandingTildeInPath] stringByAppendingPathComponent:FILE_WHITELIST]; + + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"loading whitelist %@", path]); + + //since file is created by priv'd XPC, it shouldn't be writeable + // ...unless somebody maliciously creates it, so we check if that here + if(YES == [[NSFileManager defaultManager] isWritableFileAtPath:path]) + { + //err msg + logMsg(LOG_ERR, @"whitelist is writable, so ignoring!"); + + //bail + goto bail; + } + + //load + self.whiteList = [NSMutableArray arrayWithContentsOfFile:path]; + + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"whitelist: %@", self.whiteList]); + +//bail +bail: + + return; +} + //grab first apple camera, or default // ->saves into iVar 'camera' -(void)findAppleCamera @@ -397,7 +437,6 @@ bail: return; } - //helper function // ->determines if video went active/inactive then invokes notification generator method -(void)handleVideoNotification:(CMIOObjectID)deviceID addresses:(const CMIOObjectPropertyAddress[]) addresses @@ -479,6 +518,7 @@ bail: }]; + //TODO: maybe add timeout here? //wait until XPC is done // ->XPC reply block will signal semaphore dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER); @@ -864,7 +904,8 @@ bail: } //build and display notification --(void)generateNotification:(NSDictionary*)event +// ->handles extra logic like ignore whitelisted apps, disable alerts (if user has turned that off), etc +-(void)generateNotification:(NSMutableDictionary*)event { //notification NSUserNotification* notification = nil; @@ -883,6 +924,9 @@ bail: //process name NSString* processName = nil; + //process path + NSString* processPath = nil; + //log msg NSMutableString* sysLogMsg = nil; @@ -898,44 +942,18 @@ bail: //alloc log msg sysLogMsg = [NSMutableString string]; - //check if event is essentially a duplicate (facetime, etc) - if(nil != self.lastEvent) + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"generating notification for %@", event]); + + //get process name + processName = getProcessName([event[EVENT_PROCESS_ID] intValue]); + + //get process path + processPath = getProcessPath([event[EVENT_PROCESS_ID] intValue]); + if(nil == processPath) { - //TODO: remove - //NSLog(@"difference %f", fabs([self.lastEvent[EVENT_TIMESTAMP] timeIntervalSinceDate:event[EVENT_TIMESTAMP]])); - - //less than 10 second ago? - if(fabs([self.lastEvent[EVENT_TIMESTAMP] timeIntervalSinceDate:event[EVENT_TIMESTAMP]]) < 10) - { - //same process/device/action - if( (YES == [self.lastEvent[EVENT_PROCESS_ID] isEqual:event[EVENT_PROCESS_ID]]) && - (YES == [self.lastEvent[EVENT_DEVICE] isEqual:event[EVENT_DEVICE]]) && - (YES == [self.lastEvent[EVENT_DEVICE_STATUS] isEqual:event[EVENT_DEVICE_STATUS]]) ) - { - //update - self.lastEvent = event; - - //bail to ignore - goto bail; - } - } - }//'same' event check - - //update last event - self.lastEvent = event; - - //always (manually) load preferences - preferences = [NSDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]]; - - //check if user wants to ingnore inactive alerts - if( (YES == [preferences[PREF_DISABLE_INACTIVE] boolValue]) && - (YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) ) - { - //dbg msg - logMsg(LOG_DEBUG, @"user has decided to ingore 'inactive' events, so bailing"); - - //bail - goto bail; + //set to something + processPath = PROCESS_UNKNOWN; } //set device and title for audio @@ -958,6 +976,88 @@ bail: deviceType = SOURCE_VIDEO; } + //ignore whitelisted processes + // ->for activation events, can check process path + if( (YES == [DEVICE_ACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) && + (YES == [self.whiteList containsObject:processPath]) ) + { + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"activation alert for process %@ is whitelisted, so ignoring", processPath]); + + //bail + goto bail; + } + //ignore whitelisted processes + // ->for deactivation, ignore when no activation alert was shown (cuz process will have likely died, so no pid/path, etc) + if(YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) + { + //ignore audio inactive event, if no active event was shown + if( (SOURCE_AUDIO.intValue == deviceType.intValue) && + (YES != self.showAudioDeactivation) ) + { + //dbg msg + logMsg(LOG_DEBUG, @"deactivation audio alert doesn't have an activation alert (whitelisted?), so ignoring"); + + //bail + goto bail; + } + + //ignore video inactive event, if no active event was shown + if( (SOURCE_VIDEO.intValue == deviceType.intValue) && + (YES != self.showVideoDeactivation) ) + { + //dbg msg + logMsg(LOG_DEBUG, @"deactivation video alert doesn't have an activation alert (whitelisted?), so ignoring"); + + //bail + goto bail; + } + + //dbg msg + logMsg(LOG_DEBUG, @"got deactivation alert, but neither showVideoDeactivation nor showVideoDeactivation is set..."); + } + + //check if event is essentially a duplicate (facetime, etc) + if(nil != self.lastEvent) + { + //less than 10 second ago? + if(fabs([self.lastEvent[EVENT_TIMESTAMP] timeIntervalSinceDate:event[EVENT_TIMESTAMP]]) < 10) + { + //same process/device/action + if( (YES == [self.lastEvent[EVENT_PROCESS_ID] isEqual:event[EVENT_PROCESS_ID]]) && + (YES == [self.lastEvent[EVENT_DEVICE] isEqual:event[EVENT_DEVICE]]) && + (YES == [self.lastEvent[EVENT_DEVICE_STATUS] isEqual:event[EVENT_DEVICE_STATUS]]) ) + { + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"alert for %@ would be same as previous (%@), so ignoring", event, self.lastEvent]); + + //update + self.lastEvent = event; + + //bail to ignore + goto bail; + } + } + + }//'same' event check + + //update last event + self.lastEvent = event; + + //always (manually) load preferences + preferences = [NSDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]]; + + //check if user wants to ingnore inactive alerts + if( (YES == [preferences[PREF_DISABLE_INACTIVE] boolValue]) && + (YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) ) + { + //dbg msg + logMsg(LOG_DEBUG, @"user has decided to ingore 'inactive' events, so ingoring A/V going to disable state"); + + //bail + goto bail; + } + //add action // ->device went inactive if(YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) @@ -993,9 +1093,6 @@ bail: // ->for activated audio/video; allow/block else { - //get process name - processName = getProcessName([event[EVENT_PROCESS_ID] intValue]); - //set other button title notification.otherButtonTitle = @"allow"; @@ -1004,7 +1101,7 @@ bail: //set pid/name/device into user info // ->allows code to whitelist proc and/or kill proc (later) if user clicks 'block' - notification.userInfo = @{EVENT_PROCESS_ID:event[EVENT_PROCESS_ID], EVENT_PROCESS_NAME:processName, EVENT_DEVICE:deviceType}; + notification.userInfo = @{EVENT_PROCESS_ID:event[EVENT_PROCESS_ID], EVENT_PROCESS_PATH:processPath, EVENT_PROCESS_NAME:processName, EVENT_DEVICE:deviceType}; //set details // ->name of process using it / icon too? @@ -1019,18 +1116,19 @@ bail: //no process? // ->just add title / details - if(nil == processName) + if( (nil == processName) || + (YES == [processName isEqualToString:PROCESS_UNKNOWN]) ) { //add [sysLogMsg appendFormat:@"%@ (%@)", title, details]; } //process - // ->add title / details / process path + // ->add title / details / process name / process path else { //add - [sysLogMsg appendFormat:@"%@ (process: %@, %@)", title, details, processName]; + [sysLogMsg appendFormat:@"%@ (%@, process: %@/%@)", title, details, processName, processPath]; } //write it out to syslog @@ -1052,6 +1150,41 @@ bail: //deliver notification [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; + //set flag saying we showed an 'activated' alert + // ->allows us to ignore 'inactive' events that had a whitelisted 'activate' event + if(YES == [DEVICE_ACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) + { + //audio + if(SOURCE_AUDIO.intValue == deviceType.intValue) + { + //set + self.showAudioDeactivation = YES; + } + //video + else + { + //set + self.showVideoDeactivation = YES; + } + } + //inactive alert + // ->unset flags + else + { + //audio + if(SOURCE_AUDIO.intValue == deviceType.intValue) + { + //set + self.showAudioDeactivation = NO; + } + //video + else + { + //set + self.showVideoDeactivation = NO; + } + } + //for 'went inactive' notification // ->automatically close after some time if(YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) @@ -1128,7 +1261,7 @@ bail: if(YES != notification.hasActionButton) { //dbg msg - logMsg(LOG_DEBUG, @"popup w/o an action, no need to do anything"); + logMsg(LOG_DEBUG, @"popup without an action, no need to do anything"); //bail goto bail; @@ -1159,9 +1292,23 @@ bail: //check if user clicked 'allow' via user info (since OS doesn't directly deliver this) // ->if allow was clicked, show a popup w/ option to rember ('whitelist') the application + // don't do this for 'block' since that kills the app, so obv, that'd be bad to always do! if( (nil != notification.userInfo) && (NSUserNotificationActivationTypeAdditionalActionClicked == [notification.userInfo[@"activationType"] integerValue]) ) { + //dbg msg + logMsg(LOG_DEBUG, @"user clicked 'allow'"); + + //can't remember process that we didn't find the path for + if(YES == [notification.userInfo[EVENT_PROCESS_PATH] isEqualToString:PROCESS_UNKNOWN]) + { + //dbg msg + logMsg(LOG_DEBUG, @"don't have a process path, so not displaying whitelisting popup"); + + //bail + goto bail; + } + //alloc/init settings window if(nil == self.rememberWindowController) { @@ -1177,16 +1324,13 @@ bail: //manually configure // ->invoke here as the outlets will be set - [self.rememberWindowController configure:notification]; + [self.rememberWindowController configure:notification avMonitor:self]; //make it key window [self.rememberWindowController.window makeKeyAndOrderFront:self]; //make window front [NSApp activateIgnoringOtherApps:YES]; - - //dbg msg - logMsg(LOG_DEBUG, @"user clicked 'allow'"); } //when user clicks 'block' @@ -1367,7 +1511,7 @@ bail: } //generate notification - [self generateNotification:@{EVENT_TIMESTAMP:[NSDate date], EVENT_DEVICE:self.camera, EVENT_DEVICE_STATUS:DEVICE_ACTIVE, EVENT_PROCESS_ID:processID}]; + [self generateNotification:[@{EVENT_TIMESTAMP:[NSDate date], EVENT_DEVICE:self.camera, EVENT_DEVICE_STATUS:DEVICE_ACTIVE, EVENT_PROCESS_ID:processID} mutableCopy]]; } //signal sema diff --git a/LoginItem/Info.plist b/LoginItem/Info.plist index 967a105..c77455b 100644 --- a/LoginItem/Info.plist +++ b/LoginItem/Info.plist @@ -17,17 +17,17 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.0 + 1.1.0 CFBundleSignature ???? CFBundleVersion - 1.0.0 + 1.1.0 LSUIElement LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright - Copyright (c) 2016 Objective-See. All rights reserved. + Copyright (c) 2017 Objective-See. All rights reserved. NSMainNibFile MainMenu NSPrincipalClass diff --git a/LoginItem/RemeberWindowController.h b/LoginItem/RemeberWindowController.h index d3401b2..b16fb78 100644 --- a/LoginItem/RemeberWindowController.h +++ b/LoginItem/RemeberWindowController.h @@ -8,6 +8,8 @@ #import +@class AVMonitor; + @interface RememberWindowController : NSWindowController { @@ -15,12 +17,21 @@ /* PROPERTIES */ +//process path +// ->used for whitelisting +@property (nonatomic, retain)NSString* processPath; + +//instance of av monitor +@property (nonatomic, retain)AVMonitor* avMonitor; + //version label/string @property (weak) IBOutlet NSTextField *windowText; + /* METHODS */ -//configure window w/ dynamic text --(void)configure:(NSUserNotification*)notification; +//save stuff into iVars +// ->configure window w/ dynamic text +-(void)configure:(NSUserNotification*)notification avMonitor:(AVMonitor*)monitor; @end diff --git a/LoginItem/RemeberWindowController.m b/LoginItem/RemeberWindowController.m index fb0e01f..a26cbfb 100644 --- a/LoginItem/RemeberWindowController.m +++ b/LoginItem/RemeberWindowController.m @@ -7,11 +7,18 @@ // #import "Consts.h" +#import "Logging.h" +#import "AVMonitor.h" #import "Utilities.h" +#import "../Shared/XPCProtocol.h" #import "RemeberWindowController.h" + @implementation RememberWindowController +@synthesize avMonitor; +@synthesize processPath; + //@synthesize versionLabel; //automatically called when nib is loaded @@ -50,8 +57,9 @@ } */ -//configure window w/ dynamic text --(void)configure:(NSUserNotification*)notification +//save stuff into iVars +// ->configure window w/ dynamic text +-(void)configure:(NSUserNotification*)notification avMonitor:(AVMonitor*)monitor; { //process ID NSNumber* processID = nil; @@ -62,12 +70,19 @@ //device type NSString* deviceType = nil; + //save monitor into iVar + self.avMonitor = monitor; + //grab process id processID = notification.userInfo[EVENT_PROCESS_ID]; //grab process name processName = notification.userInfo[EVENT_PROCESS_NAME]; + //grab process path + // ->saved into iVar for whitelisting + self.processPath = notification.userInfo[EVENT_PROCESS_PATH]; + //set device type for audio if(SOURCE_AUDIO.intValue == [notification.userInfo[EVENT_DEVICE] intValue]) { @@ -87,19 +102,65 @@ return; } -//automatically invoked when user clicks button 'yes' / 'no' +//automatically invoked when user clicks button 'Allow' -(IBAction)buttonHandler:(id)sender { - //handle 'always allow' (whitelist) button + //xpc connection + __block NSXPCConnection* xpcConnection = nil; + + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"handling user response for 'allow' popup: %ld", (long)((NSButton*)sender).tag]); + + //handle 'always allow' (whitelist) button if(BUTTON_ALWAYS_ALLOW == ((NSButton*)sender).tag) { - //TODO: whitelist + //init XPC + xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"]; + + //set remote object interface + xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; + + //resume + [xpcConnection resume]; + + //dbg msg + logMsg(LOG_DEBUG, @"sending XPC message to whitelist"); + + //invoke XPC method 'whitelistProcess' to add process to white list + [[xpcConnection remoteObjectProxy] whitelistProcess:self.processPath reply:^(BOOL wasWhitelisted) + { + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"got XPC response: %d", wasWhitelisted]); + + //reload whitelist on success + if(YES == wasWhitelisted) + { + //reload AVMonitor's whitelist + [self.avMonitor loadWhitelist]; + } + //err + // ->log msg + else + { + //err msg + logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to whitelist: %@", self.processPath]); + } + + //close connection + [xpcConnection invalidate]; + + //nil out + xpcConnection = nil; + + }]; } +//bail +bail: + //always close [self.window close]; - return; } @end diff --git a/LoginItem/RememberPopup.xib b/LoginItem/RememberPopup.xib index b5424be..ea493f4 100644 --- a/LoginItem/RememberPopup.xib +++ b/LoginItem/RememberPopup.xib @@ -1,7 +1,7 @@ - + - + @@ -40,19 +40,8 @@ - + diff --git a/LoginItem/main.h b/LoginItem/main.h index b71ede0..ad56f2c 100644 --- a/LoginItem/main.h +++ b/LoginItem/main.h @@ -15,4 +15,7 @@ /* FUNCTION DEFINITIONS */ +//send XPC message to remove process from whitelist file +void unWhiteList(NSString* process); + #endif /* main_h */ diff --git a/LoginItem/main.m b/LoginItem/main.m index 3d1290c..69d4eb9 100644 --- a/LoginItem/main.m +++ b/LoginItem/main.m @@ -9,6 +9,7 @@ #import "main.h" #import "Logging.h" #import "Utilities.h" +#import "../Shared/XPCProtocol.h" //go go go // ->either install/uninstall, or just launch normally @@ -20,11 +21,11 @@ int main(int argc, const char * argv[]) //dbg msg logMsg(LOG_DEBUG, [NSString stringWithFormat:@"starting login item (args: %@/user: %@)", [[NSProcessInfo processInfo] arguments], NSUserName()]); - //check for uninstall/install flags + //check for uninstall/install flags, and process to remove from whitelist if(2 == argc) { //install - if(0 == strcmp(argv[1], ACTION_INSTALL.UTF8String)) + if(0 == strcmp(argv[1], CMD_INSTALL)) { //dbg msg logMsg(LOG_DEBUG, @"running install logic"); @@ -48,7 +49,7 @@ int main(int argc, const char * argv[]) goto bail; } //uninstall - else if(0 == strcmp(argv[1], ACTION_UNINSTALL.UTF8String)) + else if(0 == strcmp(argv[1], CMD_UNINSTALL)) { //dbg msg logMsg(LOG_DEBUG, @"running uninstall logic"); @@ -68,10 +69,22 @@ int main(int argc, const char * argv[]) //dbg msg logMsg(LOG_DEBUG, [NSString stringWithFormat:@"removed preferences from: %@", [APP_PREFERENCES stringByExpandingTildeInPath]]); - //bail goto bail; } + + //assume its a path to a process to remove from whitelist + else + { + //dbg msg + logMsg(LOG_DEBUG, @"running 'un-whitelist me' logic"); + + //remove from whitelist file + unWhiteList([NSString stringWithUTF8String:argv[1]]); + + //don't bail + // ->let it start (as it was killed) + } } //launch app normally @@ -82,3 +95,45 @@ bail: return iReturn; } + +//send XPC message to remove process from whitelist file +void unWhiteList(NSString* process) +{ + //xpc connection + __block NSXPCConnection* xpcConnection = nil; + + //init XPC + xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"]; + + //set remote object interface + xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)]; + + //resume + [xpcConnection resume]; + + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"sending XPC message to remove %@ from whitelist file", process]); + + //invoke XPC method 'whitelistProcess' to add process to white list + [[xpcConnection remoteObjectProxy] unWhitelistProcess:process reply:^(BOOL wasRemoved) + { + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"got XPC response: %d", wasRemoved]); + + //err msg ono failure + if(YES != wasRemoved) + { + //err msg + logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to remove %@ from whitelist", process]); + } + + //close connection + [xpcConnection invalidate]; + + //nil out + xpcConnection = nil; + + }]; + + return; +} diff --git a/MainApp/AppDelegate.h b/MainApp/AppDelegate.h index 0a6b77b..cb96417 100644 --- a/MainApp/AppDelegate.h +++ b/MainApp/AppDelegate.h @@ -9,6 +9,8 @@ #import #import "InfoWindowController.h" #import "AboutWindowController.h" +#import "RulesWindowController.h" + @interface AppDelegate : NSObject @@ -47,6 +49,9 @@ //about window controller @property(nonatomic, retain)AboutWindowController* aboutWindowController; +//rules +@property(nonatomic, retain)RulesWindowController* rulesWindowController; + //overlay view @property (weak) IBOutlet NSView *overlay; @@ -80,8 +85,8 @@ //'manage rules' button handler -(IBAction)manageRules:(id)sender; -//start the login item --(void)startLoginItem:(BOOL)shouldRestart; +//(re)start the login item +-(void)startLoginItem:(BOOL)shouldRestart args:(NSArray*)args; @end diff --git a/MainApp/AppDelegate.m b/MainApp/AppDelegate.m index 04b20b2..cb1bb4f 100644 --- a/MainApp/AppDelegate.m +++ b/MainApp/AppDelegate.m @@ -11,7 +11,6 @@ #import "Utilities.h" #import "AppDelegate.h" - @interface AppDelegate () @property (weak) IBOutlet NSWindow *window; @@ -21,6 +20,7 @@ @synthesize infoWindowController; @synthesize aboutWindowController; +@synthesize rulesWindowController; //center window // ->also make front, init title bar, etc @@ -29,15 +29,15 @@ //center [self.window center]; + //set button states + [self setButtonStates]; + //make it key window [self.window makeKeyAndOrderFront:self]; //make window front [NSApp activateIgnoringOtherApps:YES]; - //set button states - [self setButtonStates]; - //set title self.window.title = [NSString stringWithFormat:@"OverSight Preferences (v. %@)", getAppVersion()]; @@ -48,6 +48,9 @@ // ->init user interface -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { + //dbg msg + logMsg(LOG_DEBUG, @"OverSight Preferences App Launched"); + //register for hotkey presses // ->for now, just cmd+q to quit app [self registerKeypressHandler]; @@ -69,7 +72,8 @@ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //start - [self startLoginItem:NO]; + // -> 'NO' means don't start if already running + [self startLoginItem:NO args:nil]; }); return; @@ -109,6 +113,7 @@ } //register handler for hot keys +// ->for now, it just handles cmd+q to quit -(void)registerKeypressHandler { //event handler @@ -226,12 +231,15 @@ bail: //set preferences[PREF_RUN_HEADLESS] = [NSNumber numberWithBool:[sender state]]; + //save em now so new instance of login item can read them + [preferences writeToFile:[APP_PREFERENCES stringByExpandingTildeInPath] atomically:YES]; + //restart login item in background // ->will read prefs, and run in headless mode dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //start - [self startLoginItem:YES]; + [self startLoginItem:YES args:nil]; }); } @@ -305,8 +313,11 @@ bail: //alloc string versionString = [NSMutableString string]; + //dbg msg + logMsg(LOG_DEBUG, @"checking for new version"); + //check if available version is newer - // ->show update window + // ->show update popup/window if(YES == isNewVersion(versionString)) { //dbg msg @@ -343,7 +354,7 @@ bail: } //no new version - // ->stop animations/just (debug) log msg + // ->stop animations, etc else { //dbg msg @@ -359,18 +370,17 @@ bail: self.versionLabel.hidden = NO; //set message - self.versionLabel.stringValue = @"No new versions"; + self.versionLabel.stringValue = @"no new versions"; //re-draw [self.versionLabel displayIfNeeded]; - } return; } -//start the login item --(void)startLoginItem:(BOOL)shouldRestart +//(re)start the login item +-(void)startLoginItem:(BOOL)shouldRestart args:(NSArray*)args { //path to login item NSString* loginItem = nil; @@ -378,126 +388,182 @@ bail: //login item's pid pid_t loginItemPID = -1; - //get pid of login item + //error + NSError* error = nil; + + //config (args, etc) + // ->can't be nil, so init to blank here + NSDictionary* configuration = @{}; + + //get pid of login item for user loginItemPID = getProcessID(@"OverSight Helper", getuid()); - //already running? - // ->kill the login item - if( (YES == shouldRestart) && - (-1 != loginItemPID) ) - { - //kill - kill(loginItemPID, SIGTERM); - - //sleep - sleep(2); - - //really kill - kill(loginItemPID, SIGKILL); - - //reset pid - loginItemPID = -1; - } - - //start not already running - if(-1 == loginItemPID) + //no need to start if already running + // ->well, and if 'shouldRestart' is not set + if( (-1 != loginItemPID) && + (YES != shouldRestart) ) { //dbg msg - logMsg(LOG_DEBUG, @"starting login item"); - - //show overlay view on main thread - dispatch_async(dispatch_get_main_queue(), ^{ - - //pre-req - [self.overlay setWantsLayer:YES]; - - //round edges - [self.overlay.layer setCornerRadius: 10]; - - //set overlay's view color to white - self.overlay.layer.backgroundColor = [NSColor grayColor].CGColor; - - //make it semi-transparent - self.overlay.alphaValue = 0.85; - - //show it - self.overlay.hidden = NO; - - //show message - self.statusMessage.hidden = NO; - - //show spinner - self.progressIndicator.hidden = NO; - - //animate it - [self.progressIndicator startAnimation:nil]; - - }); - } - - //init path - loginItem = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"/Contents/Library/LoginItems/OverSight Helper.app"]; - - //launch it - if(YES != [[NSWorkspace sharedWorkspace] launchApplication:loginItem]) - { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to start login item, %@", loginItem]); + logMsg(LOG_DEBUG, @"login item already running and 'shouldRestart' not set, so no need to start it!"); + + //bail + goto bail; + } + + //running? + // ->kill + else if(-1 != loginItemPID) + { + //kill it + kill(loginItemPID, SIGTERM); + + //sleep + [NSThread sleepForTimeInterval:1.0f]; + + //really kill + kill(loginItemPID, SIGKILL); + } + + //dbg msg + logMsg(LOG_DEBUG, @"starting login item"); + + //add overlay + [self addOverlay:shouldRestart]; + + //init path to login item + loginItem = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"/Contents/Library/LoginItems/OverSight Helper.app"]; + + //any args? + // ->init config with them args + if(nil != args) + { + //add args + configuration = @{NSWorkspaceLaunchConfigurationArguments:args}; + } + + //launch it + [[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL fileURLWithPath:loginItem] options:NSWorkspaceLaunchWithoutActivation configuration:configuration error:&error]; + + //remove overlay + [self removeOverlay]; + + //check if login launch was ok + // ->do down here, since always want to remove overlay + if(nil != error) + { + //err msg + logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to start login item, %@/%@", loginItem, error]); //bail goto bail; } - //(re)obtain focus for app - [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; - //bail bail: - //hide overlay? - if(-1 == loginItemPID) - { - //sleep to give message some more time - [NSThread sleepForTimeInterval:1]; + return; +} + +//add overlay to main window +-(void)addOverlay:(BOOL)restarting +{ + //show overlay view on main thread + dispatch_async(dispatch_get_main_queue(), ^{ - //update message - dispatch_async(dispatch_get_main_queue(), ^{ - - //stop spinner - [self.progressIndicator stopAnimation:nil]; - - //update - self.statusMessage.stringValue = @"started!"; - - }); + //frame + NSRect frame = {0}; - //sleep to give message some more time - [NSThread sleepForTimeInterval:1]; + //pre-req + [self.overlay setWantsLayer:YES]; + + //get main window's frame + frame = self.window.frame; + + //set origin to 0/0 + frame.origin = CGPointZero; + + //update overlay to take up entire window + self.overlay.frame = frame; + + //set overlay's view color to white + self.overlay.layer.backgroundColor = [NSColor whiteColor].CGColor; + + //make it semi-transparent + self.overlay.alphaValue = 0.85; + + //set start message + if(YES != restarting) + { + //set + self.statusMessage.stringValue = @"starting monitor..."; + } + //set restart message + else + { + //set + self.statusMessage.stringValue = @"(re)starting monitor..."; + } + + //show message + self.statusMessage.hidden = NO; + + //show spinner + self.progressIndicator.hidden = NO; + + //animate it + [self.progressIndicator startAnimation:nil]; + + //add to main window + [self.window.contentView addSubview:self.overlay]; + + //show + self.overlay.hidden = NO; + + }); + + return; +} + +//remove overlay from main window +-(void)removeOverlay +{ + //sleep to give message more viewing time + [NSThread sleepForTimeInterval:2]; + + //remove overlay view on main thread + dispatch_async(dispatch_get_main_queue(), ^{ - //hide overlay view on main thread - dispatch_async(dispatch_get_main_queue(), ^{ - //hide spinner self.progressIndicator.hidden = YES; - + //hide view self.overlay.hidden = YES; //hide message self.statusMessage.hidden = YES; - }); + //remove + [self.overlay removeFromSuperview]; - } - + }); + return; } - -//manage rules +//button handle when user clicks 'Manage Rules' +// ->just shwo the rules window -(IBAction)manageRules:(id)sender { + //alloc + rulesWindowController = [[RulesWindowController alloc] initWithWindowNibName:@"Rules"]; + + //center window + [[self.rulesWindowController window] center]; + + //show it + [self.rulesWindowController showWindow:self]; + return; } diff --git a/MainApp/Base.lproj/MainMenu.xib b/MainApp/Base.lproj/MainMenu.xib index adc4cbd..7fd2cf9 100644 --- a/MainApp/Base.lproj/MainMenu.xib +++ b/MainApp/Base.lproj/MainMenu.xib @@ -1,7 +1,7 @@ - + - + @@ -19,7 +19,7 @@ - + @@ -159,25 +159,6 @@ - @@ -205,6 +186,26 @@ + + + + + + + + + diff --git a/MainApp/Info.plist b/MainApp/Info.plist index aecb236..53abe26 100644 --- a/MainApp/Info.plist +++ b/MainApp/Info.plist @@ -15,17 +15,17 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.0 + 1.1.0 CFBundleSignature ???? CFBundleVersion - 1.0.0 + 1.1.0 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) LSUIElement NSHumanReadableCopyright - Copyright (c) 2016 Objective-See. All rights reserved. + Copyright (c) 2017 Objective-See. All rights reserved. NSMainNibFile MainMenu NSPrincipalClass diff --git a/MainApp/RuleRow.h b/MainApp/RuleRow.h new file mode 100755 index 0000000..5f1985b --- /dev/null +++ b/MainApp/RuleRow.h @@ -0,0 +1,13 @@ +// +// RuleRow.h +// OverSight +// +// Created by Patrick Wardle on 4/4/15. +// Copyright (c) 2017 Objective-See. All rights reserved. +// + +#import + +@interface RuleRow : NSTableRowView + +@end diff --git a/MainApp/RuleRow.m b/MainApp/RuleRow.m new file mode 100755 index 0000000..54eae05 --- /dev/null +++ b/MainApp/RuleRow.m @@ -0,0 +1,48 @@ +// +// RuleRow.m +// OverSight +// +// Created by Patrick Wardle on 4/4/15. +// Copyright (c) 2017 Objective-See. All rights reserved. +// + +#import "RuleRow.h" + +@implementation RuleRow + +//custom row selection +-(void)drawSelectionInRect:(NSRect)dirtyRect +{ + //selection rect + NSRect selectionRect = {0}; + + //selection path + NSBezierPath *selectionPath = nil; + + //highlight selected rows + if(self.selectionHighlightStyle != NSTableViewSelectionHighlightStyleNone) + { + //make selection rect + selectionRect = NSInsetRect(self.bounds, 2.5, 2.5); + + //set stroke + [[NSColor colorWithCalibratedWhite:.65 alpha:1.0] setStroke]; + + //set fill + [[NSColor colorWithCalibratedWhite:.82 alpha:1.0] setFill]; + + //create selection path + // ->with rounded corners + selectionPath = [NSBezierPath bezierPathWithRoundedRect:selectionRect xRadius:5 yRadius:5]; + + //fill + [selectionPath fill]; + + //stroke + [selectionPath stroke]; + } + + return; +} + +@end diff --git a/MainApp/RuleRowCell.h b/MainApp/RuleRowCell.h new file mode 100755 index 0000000..654a42f --- /dev/null +++ b/MainApp/RuleRowCell.h @@ -0,0 +1,13 @@ +// +// RuleRowCell.h +// OverSight +// +// Created by Patrick Wardle on 4/6/15. +// Copyright (c) 2017 Objective-See. All rights reserved. +// + +#import + +@interface RuleRowCell : NSTableCellView + +@end diff --git a/MainApp/RuleRowCell.m b/MainApp/RuleRowCell.m new file mode 100755 index 0000000..16b3aaa --- /dev/null +++ b/MainApp/RuleRowCell.m @@ -0,0 +1,24 @@ +// +// kkRowCell.m +// KnockKnock +// +// Created by Patrick Wardle on 4/6/15. +// Copyright (c) 2015 Objective-See. All rights reserved. +// + +#import "RuleRowCell.h" + +@implementation RuleRowCell + +-(void)drawRect:(NSRect)dirtyRect{ + [super drawRect:dirtyRect]; + + // Drawing code here. +} + +-(void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle +{ + [super setBackgroundStyle: NSBackgroundStyleLight]; +} + +@end diff --git a/MainApp/Rules.xib b/MainApp/Rules.xib new file mode 100644 index 0000000..7a288d7 --- /dev/null +++ b/MainApp/Rules.xib @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MainApp/RulesWindowController.h b/MainApp/RulesWindowController.h new file mode 100644 index 0000000..9789f58 --- /dev/null +++ b/MainApp/RulesWindowController.h @@ -0,0 +1,24 @@ +// +// RulesWindowController.h +// OverSight +// +// Created by Patrick Wardle on 7/7/16. +// Copyright (c) 2016 Objective-See. All rights reserved. +// + +#import + +@interface RulesWindowController : NSWindowController + +/* PROPERTIES */ +@property(nonatomic, retain)NSMutableArray* items; + +//table view +@property (weak) IBOutlet NSTableView *tableView; + +/* METHODS */ + +//delete a rule +- (IBAction)deleteRule:(id)sender; + +@end diff --git a/MainApp/RulesWindowController.m b/MainApp/RulesWindowController.m new file mode 100644 index 0000000..07d357b --- /dev/null +++ b/MainApp/RulesWindowController.m @@ -0,0 +1,195 @@ +// +// RulesWindowController.m +// OverSight +// +// Created by Patrick Wardle on 7/7/16. +// Copyright (c) 2016 Objective-See. All rights reserved. +// + + +#import "Consts.h" +#import "RuleRow.h" +#import "Utilities.h" +#import "AppDelegate.h" +#import "../Shared/Logging.h" +#import "RulesWindowController.h" +#import "../Shared/XPCProtocol.h" + +@interface RulesWindowController () + +@end + +@implementation RulesWindowController + +@synthesize items; + +//automatically called when nib is loaded +// ->just center window +-(void)awakeFromNib +{ + //load whitelisted items + self.items = [NSMutableArray arrayWithContentsOfFile:[[APP_SUPPORT_DIRECTORY stringByExpandingTildeInPath] stringByAppendingPathComponent:FILE_WHITELIST]]; + + return; +} + + + +/* +-(void)windowDidLoad +{ + //super + [super windowDidLoad]; + + //load whitelisted items + self.items = [NSMutableArray arrayWithContentsOfFile:[[APP_SUPPORT_DIRECTORY stringByExpandingTildeInPath] stringByAppendingPathComponent:FILE_WHITELIST]]; + + return; +} +*/ + +//table delegate +// ->return number of rows +-(NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + //count + return self.items.count; +} + +//table delegate method +// ->return cell for row +-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + //cell + NSTableCellView *tableCell = nil; + + //process name + NSString* processName = nil; + + //process path + NSString* processPath = nil; + + //process icon + NSImage* processIcon = nil; + + //app bundle + NSBundle* appBundle = nil; + + //sanity check + if(row >= self.items.count) + { + //bail + goto bail; + } + + //grab process path + processPath = [self.items objectAtIndex:row]; + + //try find an app bundle + appBundle = findAppBundle(processPath); + if(nil != appBundle) + { + //grab name from app's bundle + processName = [appBundle infoDictionary][@"CFBundleName"]; + } + + //still nil? + // ->just grab from path + if(nil == processName) + { + //from path + processName = [processPath lastPathComponent]; + } + + //grab icon + processIcon = getIconForProcess(processPath); + + //init table cell + tableCell = [tableView makeViewWithIdentifier:@"itemCell" owner:self]; + if(nil == tableCell) + { + //bail + goto bail; + } + + //set icon + tableCell.imageView.image = processIcon; + + //set (main) text + tableCell.textField.stringValue = processName; + + //set sub text + [[tableCell viewWithTag:TABLE_ROW_SUB_TEXT_TAG] setStringValue:processPath]; + + //set detailed text color to gray + ((NSTextField*)[tableCell viewWithTag:TABLE_ROW_SUB_TEXT_TAG]).textColor = [NSColor grayColor]; + +//bail +bail: + + // Return the result + return tableCell; + +} + +//automatically invoked +// ->create custom (sub-classed) NSTableRowView +-(NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row +{ + //row view + RuleRow* rowView = nil; + + //row ID + static NSString* const kRowIdentifier = @"RowView"; + + //try grab existing row view + rowView = [tableView makeViewWithIdentifier:kRowIdentifier owner:self]; + + //make new if needed + if(nil == rowView) + { + //create new + // ->size doesn't matter + rowView = [[RuleRow alloc] initWithFrame:NSZeroRect]; + + //set row ID + rowView.identifier = kRowIdentifier; + } + + return rowView; +} + +//delete a whitelist item +// ->gotta invoke the login item, as it can do that via XPC +-(IBAction)deleteRule:(id)sender +{ + //index of selected row + NSInteger selectedRow = 0; + + //rule + NSString* processPath = nil; + + //grab selected row + selectedRow = [self.tableView rowForView:sender]; + + //extract selected item + processPath = self.items[selectedRow]; + + //remove from items + [self.items removeObject:processPath]; + + //reload table + [self.tableView reloadData]; + + //restart login item in background + // ->pass in process name so it can un-whitelist via XPC + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + ^{ + //restart + [((AppDelegate*)[[NSApplication sharedApplication] delegate]) startLoginItem:YES args:@[processPath]]; + }); + + return; +} + +@end diff --git a/OverSight.xcodeproj/project.pbxproj b/OverSight.xcodeproj/project.pbxproj index d2aa55c..970148e 100644 --- a/OverSight.xcodeproj/project.pbxproj +++ b/OverSight.xcodeproj/project.pbxproj @@ -37,6 +37,10 @@ 7D9A7DEC1D8BE1E00091C1AF /* Exception.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D17C5311D659E580066232A /* Exception.m */; }; 7D9A7DF21D8F2C900091C1AF /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7D6245841D87C43900870565 /* Images.xcassets */; }; 7DAF4B7F1D657192000DA31A /* StatusBarMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DAF4B7D1D656FD3000DA31A /* StatusBarMenu.m */; }; + 7DC038021E87025100349474 /* RulesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC038001E87025100349474 /* RulesWindowController.m */; }; + 7DC038031E87025100349474 /* Rules.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7DC038011E87025100349474 /* Rules.xib */; }; + 7DC038061E8716F700349474 /* RuleRowCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC038051E8716F700349474 /* RuleRowCell.m */; }; + 7DC038091E8717B200349474 /* RuleRow.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC038081E8717B200349474 /* RuleRow.m */; }; 7DC9C8171D641A350017D143 /* OverSightXPC.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC9C8161D641A350017D143 /* OverSightXPC.m */; }; 7DC9C8191D641A350017D143 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC9C8181D641A350017D143 /* main.m */; }; 8B5755A119DA3E9500799E6B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5755A019DA3E9500799E6B /* main.m */; }; @@ -125,6 +129,13 @@ 7D9A7DEF1D8CADD10091C1AF /* main.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = main.h; path = OverSightXPC/main.h; sourceTree = ""; }; 7DAF4B7C1D656FD3000DA31A /* StatusBarMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusBarMenu.h; sourceTree = ""; }; 7DAF4B7D1D656FD3000DA31A /* StatusBarMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusBarMenu.m; sourceTree = ""; }; + 7DC037FF1E87025100349474 /* RulesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RulesWindowController.h; sourceTree = ""; }; + 7DC038001E87025100349474 /* RulesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RulesWindowController.m; sourceTree = ""; }; + 7DC038011E87025100349474 /* Rules.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Rules.xib; sourceTree = ""; }; + 7DC038041E8716F700349474 /* RuleRowCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuleRowCell.h; sourceTree = ""; }; + 7DC038051E8716F700349474 /* RuleRowCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuleRowCell.m; sourceTree = ""; }; + 7DC038071E8717B200349474 /* RuleRow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuleRow.h; sourceTree = ""; }; + 7DC038081E8717B200349474 /* RuleRow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuleRow.m; sourceTree = ""; }; 7DC9C8121D641A350017D143 /* OverSightXPC.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = OverSightXPC.xpc; sourceTree = BUILT_PRODUCTS_DIR; }; 7DC9C8151D641A350017D143 /* OverSightXPC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OverSightXPC.h; sourceTree = ""; }; 7DC9C8161D641A350017D143 /* OverSightXPC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OverSightXPC.m; sourceTree = ""; }; @@ -262,9 +273,16 @@ 8B57559D19DA3E9500799E6B /* MainApp */ = { isa = PBXGroup; children = ( + 7DC038071E8717B200349474 /* RuleRow.h */, + 7DC038081E8717B200349474 /* RuleRow.m */, + 7DC038041E8716F700349474 /* RuleRowCell.h */, + 7DC038051E8716F700349474 /* RuleRowCell.m */, 8B5755A219DA3E9500799E6B /* AppDelegate.h */, 8B5755A319DA3E9500799E6B /* AppDelegate.m */, 8B5755A719DA3E9500799E6B /* MainMenu.xib */, + 7DC037FF1E87025100349474 /* RulesWindowController.h */, + 7DC038001E87025100349474 /* RulesWindowController.m */, + 7DC038011E87025100349474 /* Rules.xib */, 8B57559E19DA3E9500799E6B /* Supporting Files */, ); path = MainApp; @@ -371,7 +389,7 @@ 8B57559319DA3E9500799E6B /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0810; + LastUpgradeCheck = 0820; ORGANIZATIONNAME = "Cory Bohon"; TargetAttributes = { 7DC9C8111D641A350017D143 = { @@ -431,6 +449,7 @@ buildActionMask = 2147483647; files = ( 7D9A7DF21D8F2C900091C1AF /* Images.xcassets in Resources */, + 7DC038031E87025100349474 /* Rules.xib in Resources */, 7D6245921D87D46800870565 /* InfoWindow.xib in Resources */, 7D17C53C1D659E580066232A /* icon.png in Resources */, 8B5755A919DA3E9500799E6B /* MainMenu.xib in Resources */, @@ -474,12 +493,15 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7DC038061E8716F700349474 /* RuleRowCell.m in Sources */, + 7DC038021E87025100349474 /* RulesWindowController.m in Sources */, 7D6245911D87D39E00870565 /* InfoWindowController.m in Sources */, 7D62458B1D87CE0900870565 /* AboutWindowController.m in Sources */, 7D6245891D87C58400870565 /* Logging.m in Sources */, 8B5755A419DA3E9500799E6B /* AppDelegate.m in Sources */, 8B5755A119DA3E9500799E6B /* main.m in Sources */, 7D17C53B1D659E580066232A /* Exception.m in Sources */, + 7DC038091E8717B200349474 /* RuleRow.m in Sources */, 7D17C53F1D659E580066232A /* Utilities.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -586,6 +608,7 @@ CODE_SIGN_IDENTITY = "Developer ID Application: Objective-See, LLC (VBG97UB4TA)"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; diff --git a/OverSightXPC/Enumerator.m b/OverSightXPC/Enumerator.m index b732347..dcd90f9 100644 --- a/OverSightXPC/Enumerator.m +++ b/OverSightXPC/Enumerator.m @@ -385,6 +385,15 @@ bail: //assign candidateAudioProcs = [[intersection allObjects] mutableCopy]; + //if there aren't any new i/o registy clients and only one new mach sender + // ->use that! (e.g. Siri, reactivated) + if( (0 == candidateAudioProcs.count) && + (1 == newSenders.count) ) + { + //assign as candidate + [candidateAudioProcs addObject:newSenders.firstObject]; + } + //dbg msg logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu candidate audio procs: %@", (unsigned long)candidateAudioProcs.count, candidateAudioProcs]); diff --git a/OverSightXPC/OverSightXPC.m b/OverSightXPC/OverSightXPC.m index adab759..e591fd9 100644 --- a/OverSightXPC/OverSightXPC.m +++ b/OverSightXPC/OverSightXPC.m @@ -6,6 +6,7 @@ // Copyright (c) 2016 Objective-See. All rights reserved. // +#import "Consts.h" #import "Logging.h" #import "Utilities.h" #import "Enumerator.h" @@ -75,6 +76,129 @@ return; } +//whitelist a process +-(void)whitelistProcess:(NSString*)processPath reply:(void (^)(BOOL))reply +{ + //flag + BOOL wasAdded = NO; + + //path to whitelist + NSString* path = nil; + + //whitelist + NSMutableArray* whiteList = nil; + + //error + NSError* error = nil; + + //init path to whitelist + path = [[APP_SUPPORT_DIRECTORY stringByExpandingTildeInPath] stringByAppendingPathComponent:FILE_WHITELIST]; + + //load whitelist + whiteList = [NSMutableArray arrayWithContentsOfFile:path]; + + //failed to load + // ->might not exist yet, so alloc + if(nil == whiteList) + { + //alloc + whiteList = [NSMutableArray array]; + } + + //add + [whiteList addObject:processPath]; + + //check if intermediate dirs exist + // ->create them if they aren't there yet + if(YES != [[NSFileManager defaultManager] fileExistsAtPath:[APP_SUPPORT_DIRECTORY stringByExpandingTildeInPath]]) + { + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"XPC: creating app support directory: %@", [APP_SUPPORT_DIRECTORY stringByExpandingTildeInPath]]); + + //create + if(YES != [[NSFileManager defaultManager] createDirectoryAtPath: [APP_SUPPORT_DIRECTORY stringByExpandingTildeInPath] withIntermediateDirectories:YES attributes:nil error:&error]) + { + //err msg + logMsg(LOG_ERR, [NSString stringWithFormat:@"XPC: failed to create directory: %@", error]); + + //bail + goto bail; + } + } + + //save to disk + if(YES != [whiteList writeToFile:path atomically:YES]) + { + //err msg + logMsg(LOG_ERR, [NSString stringWithFormat:@"XPC: failed to save %@ -> %@", processPath, path]); + + //bail + goto bail; + } + + //happy + wasAdded = YES; + +//bail +bail: + + //reply + reply(wasAdded); + + return; +} + +//remove a process from the whitelist file +-(void)unWhitelistProcess:(NSString*)processPath reply:(void (^)(BOOL))reply +{ + //flag + BOOL wasRemoved = NO; + + //path to whitelist + NSString* path = nil; + + //whitelist + NSMutableArray* whiteList = nil; + + //init path to whitelist + path = [[APP_SUPPORT_DIRECTORY stringByExpandingTildeInPath] stringByAppendingPathComponent:FILE_WHITELIST]; + + //load whitelist + whiteList = [NSMutableArray arrayWithContentsOfFile:path]; + if(nil == whiteList) + { + //err msg + logMsg(LOG_ERR, [NSString stringWithFormat:@"XPC: failed to load whitelist from %@", path]); + + //bail + goto bail; + } + + //remove item + [whiteList removeObject:processPath]; + + //save to disk + if(YES != [whiteList writeToFile:path atomically:YES]) + { + //err msg + logMsg(LOG_ERR, [NSString stringWithFormat:@"XPC: failed to save updated whitelist to %@", path]); + + //bail + goto bail; + } + + //happy + wasRemoved = YES; + +//bail +bail: + + //reply + reply(wasRemoved); + + return; +} + //kill a process -(void)killProcess:(NSNumber*)processID reply:(void (^)(BOOL))reply { @@ -85,7 +209,7 @@ if(-1 == kill(processID.intValue, SIGKILL)) { //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to kill %@, with %d", processID, errno]); + logMsg(LOG_ERR, [NSString stringWithFormat:@"XPC: failed to kill %@, with %d", processID, errno]); //bail goto bail; diff --git a/OverSightXPC/main.h b/OverSightXPC/main.h index 63226e9..7a43b43 100644 --- a/OverSightXPC/main.h +++ b/OverSightXPC/main.h @@ -3,7 +3,7 @@ // OverSight // // Created by Patrick Wardle on 9/16/16. -// Copyright © 2016 Cory Bohon. All rights reserved. +// Copyright (c) 2016 Objective-See. All rights reserved. // #ifndef main_h diff --git a/Shared/Consts.h b/Shared/Consts.h index bb01528..32ad1be 100644 --- a/Shared/Consts.h +++ b/Shared/Consts.h @@ -25,7 +25,7 @@ #define PRODUCT_URL @"https://objective-see.com/products/oversight.html" //product version url -#define PRODUCT_VERSION_URL @"https://objective-see.com/products/versions/oversight.json" +#define PRODUCT_VERSION_URL @"https://objective-see.com/products.json" //OS version x #define OS_MAJOR_VERSION_X 10 @@ -50,12 +50,22 @@ // ->also button title #define ACTION_UNINSTALL @"Uninstall" +//button title +// ->Close +#define ACTION_CLOSE @"Close" + //flag to uninstall #define ACTION_UNINSTALL_FLAG 0 //flag to install #define ACTION_INSTALL_FLAG 1 +//flag for partial uninstall (leave whitelist) +#define UNINSTALL_PARIAL 0 + +//flag for full uninstall +#define UNINSTALL_FULL 1 + //error msg #define KEY_ERROR_MSG @"errorMsg" @@ -107,14 +117,22 @@ //path to facetime #define FACE_TIME @"/Applications/FaceTime.app/Contents/MacOS/FaceTime" +//app support directory +#define APP_SUPPORT_DIRECTORY @"~/Library/Application Support/Objective-See/OverSight" + +//whitelist +#define FILE_WHITELIST @"whitelist.plist" + //event keys -//#define EVENT_SOURCE @"source" #define EVENT_DEVICE @"device" -#define EVENT_TIMESTAMP @"timestamp" +#define EVENT_TIMESTAMP @"timeStamp" #define EVENT_DEVICE_STATUS @"status" #define EVENT_PROCESS_ID @"processID" #define EVENT_PROCESS_NAME @"processName" +#define EVENT_PROCESS_PATH @"processPath" +//unknown process +#define PROCESS_UNKNOWN @"" //source audio #define SOURCE_AUDIO @0x1 @@ -128,6 +146,8 @@ //no/close button #define BUTTON_NO 101 +//id (tag) for detailed text in rules table +#define TABLE_ROW_SUB_TEXT_TAG 101 #endif diff --git a/Shared/Logging.m b/Shared/Logging.m index e79e8b9..d48badb 100644 --- a/Shared/Logging.m +++ b/Shared/Logging.m @@ -6,8 +6,6 @@ // Copyright (c) 2016 Objective-See. All rights reserved. // -//TODO: add os_log_with_type for 10.12+ - #import "Consts.h" #import "Logging.h" #import "Utilities.h" diff --git a/Shared/Utilities.h b/Shared/Utilities.h index 1cc8d16..a76d437 100644 --- a/Shared/Utilities.h +++ b/Shared/Utilities.h @@ -1,6 +1,6 @@ // // Utilities.h -// WhatsYourSign +// OverSight // // Created by Patrick Wardle on 7/7/16. // Copyright (c) 2016 Objective-See. All rights reserved. @@ -59,6 +59,10 @@ NSString* getProcessName(pid_t pid); // ->get the (first) instance of that process pid_t getProcessID(NSString* processName, uid_t userID); +//get an icon for a process +// ->for apps, this will be app's icon, otherwise just a standard system one +NSImage* getIconForProcess(NSString* path); + //wait until a window is non nil // ->then make it modal void makeModal(NSWindowController* windowController); diff --git a/Shared/Utilities.m b/Shared/Utilities.m index ceacc35..86fb635 100644 --- a/Shared/Utilities.m +++ b/Shared/Utilities.m @@ -1,6 +1,6 @@ // // Utilities.m -// WhatsYourSign +// OverSight // // Created by Patrick Wardle on 7/7/16. // Copyright (c) 2016 Objective-See. All rights reserved. @@ -611,6 +611,89 @@ bail: return processID; } + +//get an icon for a process +// ->for apps, this will be app's icon, otherwise just a standard system one +NSImage* getIconForProcess(NSString* path) +{ + //icon's file name + NSString* iconFile = nil; + + //icon's path + NSString* iconPath = nil; + + //icon's path extension + NSString* iconExtension = nil; + + //icon + NSImage* icon = nil; + + //system's document icon + static NSData* documentIcon = nil; + + //bundle + NSBundle* appBundle = nil; + + //first try grab bundle + // ->then extact icon from this + appBundle = findAppBundle(path); + if(nil != appBundle) + { + //get file + iconFile = appBundle.infoDictionary[@"CFBundleIconFile"]; + + //get path extension + iconExtension = [iconFile pathExtension]; + + //if its blank (i.e. not specified) + // ->go with 'icns' + if(YES == [iconExtension isEqualTo:@""]) + { + //set type + iconExtension = @"icns"; + } + + //set full path + iconPath = [appBundle pathForResource:[iconFile stringByDeletingPathExtension] ofType:iconExtension]; + + //load it + icon = [[NSImage alloc] initWithContentsOfFile:iconPath]; + } + + //process is not an app or couldn't get icon + // ->try to get it via shared workspace + if( (nil == appBundle) || + (nil == icon) ) + { + //extract icon + icon = [[NSWorkspace sharedWorkspace] iconForFile:path]; + + //load system document icon + // ->static var, so only load once + if(nil == documentIcon) + { + //load + documentIcon = [[[NSWorkspace sharedWorkspace] iconForFileType: + NSFileTypeForHFSTypeCode(kGenericDocumentIcon)] TIFFRepresentation]; + } + + //if 'iconForFile' method doesn't find and icon, it returns the system 'document' icon + // ->the system 'applicatoon' icon seems more applicable, so use that here... + if(YES == [[icon TIFFRepresentation] isEqual:documentIcon]) + { + //set icon to system 'applicaiton' icon + icon = [[NSWorkspace sharedWorkspace] + iconForFileType: NSFileTypeForHFSTypeCode(kGenericApplicationIcon)]; + } + + //'iconForFileType' returns small icons + // ->so set size to 128 + [icon setSize:NSMakeSize(128, 128)]; + } + + return icon; +} + //determine if there is a new version // -1, YES or NO NSInteger isNewVersion(NSMutableString* versionString) @@ -628,7 +711,7 @@ NSInteger isNewVersion(NSMutableString* versionString) installedVersion = getAppVersion(); //get latest version - // ->will query internet (bb's website) + // ->will query internet (obj-see website) latestVersion = getLatestVersion(); if(nil == latestVersion) { @@ -656,7 +739,7 @@ bail: NSString* getLatestVersion() { //version data - NSData* versionData = nil; + __block NSData* versionData = nil; //version dictionary NSDictionary* versionDictionary = nil; @@ -664,8 +747,22 @@ NSString* getLatestVersion() //latest version NSString* latestVersion = nil; - //get version from remote URL - versionData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:PRODUCT_VERSION_URL]]; + //run in background if main thread + if(YES == [NSThread isMainThread]) + { + //run in background + dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + ^{ + //get version data + versionData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:PRODUCT_VERSION_URL]]; + }); + } + //no need to background + else + { + //get version from remote URL + versionData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:PRODUCT_VERSION_URL]]; + } //sanity check if(nil == versionData) @@ -958,5 +1055,3 @@ bail: return processID; } - - diff --git a/Shared/XPCProtocol.h b/Shared/XPCProtocol.h index 3e6cafe..389ed58 100644 --- a/Shared/XPCProtocol.h +++ b/Shared/XPCProtocol.h @@ -28,6 +28,12 @@ // ->allows enumerator to stop baselining (when active), etc -(void)updateAudioStatus:(unsigned int)status reply:(void (^)(void))reply; +//whitelist a process +-(void)whitelistProcess:(NSString*)processPath reply:(void (^)(BOOL))reply; + +//remove a process from the whitelist file +-(void)unWhitelistProcess:(NSString*)processPath reply:(void (^)(BOOL))reply; + //kill a process -(void)killProcess:(NSNumber*)processID reply:(void (^)(BOOL))reply;