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;