logic for version 1.1.0 :)

-whitelist / rules logic
 -improved audio process enumeration
This commit is contained in:
Patrick Wardle 2017-03-27 22:00:11 -10:00
parent eb635e54d9
commit c6eaba30f2
33 changed files with 1510 additions and 296 deletions

View File

@ -29,7 +29,7 @@
-(BOOL)install;
//uninstall
-(BOOL)uninstall;
-(BOOL)uninstall:(NSUInteger)type;
@end

View File

@ -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

View File

@ -7,6 +7,7 @@
//
#import "Consts.h"
#import "Logging.h"
#import "Configure.h"
#import "Utilities.h"
#import "ConfigureWindowController.h"
@ -108,6 +109,21 @@
//action
NSUInteger action = 0;
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"handling action click: %@", buttonTitle]);
//close?
// ->just exit
if(YES == [buttonTitle isEqualToString:ACTION_CLOSE])
{
//close
[self.window close];
}
//install/uninstall logic handlers
else
{
//hide 'get more info' button
self.moreInfoButton.hidden = YES;
@ -145,9 +161,7 @@
[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];

View File

@ -17,15 +17,15 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>1.1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<string>1.1.0</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright (c) 2016 Objective-See. All rights reserved.</string>
<string>Copyright (c) 2017 Objective-See. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>

View File

@ -11,6 +11,8 @@
#import "Configure.h"
#import <Cocoa/Cocoa.h>
//TODO: wrap debug msgs!
int main(int argc, const char * argv[])
{
//return var

View File

@ -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;

View File

@ -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)
{
//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");
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"generating notification for %@", event]);
//bail
goto bail;
//get process name
processName = getProcessName([event[EVENT_PROCESS_ID] intValue]);
//get process path
processPath = getProcessPath([event[EVENT_PROCESS_ID] intValue]);
if(nil == processPath)
{
//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

View File

@ -17,17 +17,17 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>1.1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<string>1.1.0</string>
<key>LSUIElement</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright (c) 2016 Objective-See. All rights reserved.</string>
<string>Copyright (c) 2017 Objective-See. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>

View File

@ -8,6 +8,8 @@
#import <Cocoa/Cocoa.h>
@class AVMonitor;
@interface RememberWindowController : NSWindowController <NSWindowDelegate>
{
@ -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

View File

@ -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
{
//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

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11542" systemVersion="16B2555" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11542"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -40,19 +40,8 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" fixedFrame="YES" tag="101" translatesAutoresizingMaskIntoConstraints="NO" id="HZZ-Es-mpy">
<rect key="frame" x="352" y="11" width="56" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="no" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="J9x-sM-h9S">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" size="13" name="Menlo-Regular"/>
</buttonCell>
<connections>
<action selector="buttonHandler:" target="-2" id="yBL-R5-DDG"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" tag="100" translatesAutoresizingMaskIntoConstraints="NO" id="RKg-ba-EQ4">
<rect key="frame" x="202" y="11" width="127" height="32"/>
<rect key="frame" x="177" y="11" width="127" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="yes, always" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="0OU-M1-ErX">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@ -62,6 +51,17 @@
<action selector="buttonHandler:" target="-2" id="LNd-1i-TBg"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" tag="101" translatesAutoresizingMaskIntoConstraints="NO" id="HZZ-Es-mpy">
<rect key="frame" x="310" y="11" width="98" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="just once" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="J9x-sM-h9S">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" size="13" name="Menlo-Regular"/>
</buttonCell>
<connections>
<action selector="buttonHandler:" target="-2" id="yBL-R5-DDG"/>
</connections>
</button>
</subviews>
</view>
<connections>

View File

@ -15,4 +15,7 @@
/* FUNCTION DEFINITIONS */
//send XPC message to remove process from whitelist file
void unWhiteList(NSString* process);
#endif /* main_h */

View File

@ -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;
}

View File

@ -9,6 +9,8 @@
#import <Cocoa/Cocoa.h>
#import "InfoWindowController.h"
#import "AboutWindowController.h"
#import "RulesWindowController.h"
@interface AppDelegate : NSObject <NSApplicationDelegate>
@ -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

View File

@ -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,50 +388,122 @@ 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) )
//no need to start if already running
// ->well, and if 'shouldRestart' is not set
if( (-1 != loginItemPID) &&
(YES != shouldRestart) )
{
//kill
//dbg msg
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
sleep(2);
[NSThread sleepForTimeInterval:1.0f];
//really kill
kill(loginItemPID, SIGKILL);
//reset pid
loginItemPID = -1;
}
//start not already running
if(-1 == loginItemPID)
{
//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;
}
//bail
bail:
return;
}
//add overlay to main window
-(void)addOverlay:(BOOL)restarting
{
//show overlay view on main thread
dispatch_async(dispatch_get_main_queue(), ^{
//frame
NSRect frame = {0};
//pre-req
[self.overlay setWantsLayer:YES];
//round edges
[self.overlay.layer setCornerRadius: 10];
//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 grayColor].CGColor;
self.overlay.layer.backgroundColor = [NSColor whiteColor].CGColor;
//make it semi-transparent
self.overlay.alphaValue = 0.85;
//show it
self.overlay.hidden = NO;
//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;
@ -432,50 +514,24 @@ bail:
//animate it
[self.progressIndicator startAnimation:nil];
});
}
//add to main window
[self.window.contentView addSubview:self.overlay];
//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]);
//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];
//update message
dispatch_async(dispatch_get_main_queue(), ^{
//stop spinner
[self.progressIndicator stopAnimation:nil];
//update
self.statusMessage.stringValue = @"started!";
//show
self.overlay.hidden = NO;
});
//sleep to give message some more time
[NSThread sleepForTimeInterval:1];
return;
}
//hide overlay view on main thread
//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 spinner
@ -487,17 +543,27 @@ bail:
//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;
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11542" systemVersion="16B2555" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11542"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -19,7 +19,7 @@
<outlet property="check4UpdatesNow" destination="M9W-tZ-0Z8" id="PKA-ah-dde"/>
<outlet property="disableInactive" destination="Nf6-uL-uwr" id="7js-Vr-2nI"/>
<outlet property="logActivity" destination="rVS-uv-DNf" id="5XJ-Ec-mNt"/>
<outlet property="overlay" destination="bHg-M3-YdC" id="dTJ-FV-0Bt"/>
<outlet property="overlay" destination="ewJ-2y-7Qh" id="tzX-7g-UCn"/>
<outlet property="progressIndicator" destination="K08-2Z-AIq" id="0K0-Ao-9Ad"/>
<outlet property="runHeadless" destination="7bS-hN-Wc7" id="dbA-Sv-fUR"/>
<outlet property="spinner" destination="sph-QU-dL4" id="uwr-2X-sg8"/>
@ -159,25 +159,6 @@
<action selector="manageRules:" target="Voe-Tx-rLC" id="UuK-ti-hYN"/>
</connections>
</button>
<customView hidden="YES" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bHg-M3-YdC">
<rect key="frame" x="27" y="116" width="426" height="155"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="u4r-or-Aue">
<rect key="frame" x="-5" y="68" width="437" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="starting monitor...." id="vap-C0-2c8">
<font key="font" size="13" name="Menlo-Regular"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<progressIndicator hidden="YES" wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" maxValue="100" bezeled="NO" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="K08-2Z-AIq">
<rect key="frame" x="197" y="96" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</progressIndicator>
</subviews>
</customView>
</subviews>
</view>
</window>
@ -205,6 +186,26 @@
</items>
<point key="canvasLocation" x="-49" y="-382"/>
</menu>
<customView id="ewJ-2y-7Qh" userLabel="Overlay">
<rect key="frame" x="0.0" y="0.0" width="480" height="360"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="u4r-or-Aue">
<rect key="frame" x="18" y="164" width="444" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="message..." id="vap-C0-2c8">
<font key="font" size="17" name="Menlo-Bold"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<progressIndicator hidden="YES" wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" maxValue="100" bezeled="NO" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="K08-2Z-AIq">
<rect key="frame" x="224" y="197" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</progressIndicator>
</subviews>
<point key="canvasLocation" x="765" y="150"/>
</customView>
</objects>
<resources>
<image name="icon" width="512" height="512"/>

View File

@ -15,17 +15,17 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>1.1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<string>1.1.0</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key>
<false/>
<key>NSHumanReadableCopyright</key>
<string>Copyright (c) 2016 Objective-See. All rights reserved.</string>
<string>Copyright (c) 2017 Objective-See. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>

13
MainApp/RuleRow.h Executable file
View File

@ -0,0 +1,13 @@
//
// RuleRow.h
// OverSight
//
// Created by Patrick Wardle on 4/4/15.
// Copyright (c) 2017 Objective-See. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface RuleRow : NSTableRowView
@end

48
MainApp/RuleRow.m Executable file
View File

@ -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

13
MainApp/RuleRowCell.h Executable file
View File

@ -0,0 +1,13 @@
//
// RuleRowCell.h
// OverSight
//
// Created by Patrick Wardle on 4/6/15.
// Copyright (c) 2017 Objective-See. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface RuleRowCell : NSTableCellView
@end

24
MainApp/RuleRowCell.m Executable file
View File

@ -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

161
MainApp/Rules.xib Normal file
View File

@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="RulesWindowController">
<connections>
<outlet property="tableView" destination="rpa-sZ-jQp" id="SvF-Yi-OKB"/>
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="White Listed Applications" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="611" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1058"/>
<value key="minSize" type="size" width="500" height="100"/>
<view key="contentView" wantsLayer="YES" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="611" height="270"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView autohidesScrollers="YES" horizontalLineScroll="56" horizontalPageScroll="10" verticalLineScroll="56" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gth-To-Lf2">
<rect key="frame" x="-1" y="-1" width="613" height="272"/>
<clipView key="contentView" id="ubO-Ur-NA2">
<rect key="frame" x="1" y="0.0" width="611" height="271"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnSelection="YES" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="54" rowSizeStyle="automatic" headerView="MjO-gV-r1W" viewBased="YES" id="rpa-sZ-jQp">
<rect key="frame" x="0.0" y="0.0" width="611" height="248"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="608" minWidth="40" maxWidth="1000" id="ocl-vV-SZu">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="pc6-VQ-D0H">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="itemCell" id="moN-VP-hzI" customClass="RuleRowCell">
<rect key="frame" x="1" y="1" width="608" height="54"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView translatesAutoresizingMaskIntoConstraints="NO" id="JiF-Nx-dl5">
<rect key="frame" x="3" y="7" width="40" height="40"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSColorPanel" id="v3A-16-OyI"/>
</imageView>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" tag="100" translatesAutoresizingMaskIntoConstraints="NO" id="JMN-i9-vxR">
<rect key="frame" x="54" y="21" width="523" height="26"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Item Category" id="2GD-5k-sEf">
<font key="font" size="17" name="Menlo-Bold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" tag="101" translatesAutoresizingMaskIntoConstraints="NO" id="w9P-yZ-pho">
<rect key="frame" x="54" y="8" width="523" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="519" id="kmq-rD-zZS"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Item Category Description" id="EZN-NC-GXx">
<font key="font" size="11" name="Menlo-Regular"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aWW-9r-3lC">
<rect key="frame" x="583" y="19" width="14" height="15"/>
<buttonCell key="cell" type="bevel" bezelStyle="regularSquare" image="NSStopProgressFreestandingTemplate" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" inset="2" id="wdw-rZ-IR3">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="deleteRule:" target="-2" id="vM0-zk-yrN"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="JiF-Nx-dl5" firstAttribute="top" secondItem="moN-VP-hzI" secondAttribute="top" constant="7" id="0og-eM-P5A"/>
<constraint firstItem="aWW-9r-3lC" firstAttribute="top" secondItem="moN-VP-hzI" secondAttribute="top" constant="20" id="2Hq-gF-RRx"/>
<constraint firstAttribute="bottom" secondItem="JMN-i9-vxR" secondAttribute="bottom" constant="21" id="2lm-V9-MgG"/>
<constraint firstItem="w9P-yZ-pho" firstAttribute="trailing" secondItem="JMN-i9-vxR" secondAttribute="trailing" id="3zL-0B-Yt0"/>
<constraint firstItem="JMN-i9-vxR" firstAttribute="top" secondItem="JiF-Nx-dl5" secondAttribute="top" id="4ZQ-hO-YeB"/>
<constraint firstAttribute="bottom" secondItem="aWW-9r-3lC" secondAttribute="bottom" constant="19" id="JXs-JW-Xg3"/>
<constraint firstItem="w9P-yZ-pho" firstAttribute="top" secondItem="moN-VP-hzI" secondAttribute="top" constant="29" id="Rbh-tU-0B0"/>
<constraint firstItem="JiF-Nx-dl5" firstAttribute="leading" secondItem="moN-VP-hzI" secondAttribute="leading" constant="3" id="UxO-Jr-Zzk"/>
<constraint firstItem="w9P-yZ-pho" firstAttribute="leading" secondItem="JMN-i9-vxR" secondAttribute="leading" id="d1t-pb-hcW"/>
<constraint firstItem="w9P-yZ-pho" firstAttribute="leading" secondItem="JiF-Nx-dl5" secondAttribute="trailing" constant="13" id="dD6-u5-DZw"/>
<constraint firstItem="w9P-yZ-pho" firstAttribute="leading" secondItem="moN-VP-hzI" secondAttribute="leading" constant="56" id="mQY-GF-Ndc"/>
<constraint firstAttribute="trailing" secondItem="aWW-9r-3lC" secondAttribute="trailing" constant="11" id="n4e-65-kcp"/>
<constraint firstAttribute="bottom" secondItem="JiF-Nx-dl5" secondAttribute="bottom" constant="7" id="na5-b4-JAs"/>
</constraints>
<connections>
<outlet property="imageView" destination="JiF-Nx-dl5" id="pVf-M3-mAH"/>
<outlet property="textField" destination="JMN-i9-vxR" id="sds-eR-7bO"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
<connections>
<outlet property="dataSource" destination="-2" id="cSg-At-Ff8"/>
<outlet property="delegate" destination="-2" id="gZb-ih-AfV"/>
</connections>
</tableView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="VKw-SH-33M">
<rect key="frame" x="1" y="255" width="480" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="ecy-l3-ARc">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" id="MjO-gV-r1W">
<rect key="frame" x="0.0" y="0.0" width="611" height="23"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
</subviews>
<constraints>
<constraint firstItem="gth-To-Lf2" firstAttribute="top" secondItem="se5-gp-TjO" secondAttribute="top" constant="-1" id="0Jc-HW-sRY"/>
<constraint firstAttribute="trailing" secondItem="gth-To-Lf2" secondAttribute="trailing" constant="-1" id="GjA-Ta-LSr"/>
<constraint firstAttribute="bottom" secondItem="gth-To-Lf2" secondAttribute="bottom" constant="-1" id="gFc-Mm-n7w"/>
<constraint firstItem="gth-To-Lf2" firstAttribute="leading" secondItem="se5-gp-TjO" secondAttribute="leading" constant="-1" id="jrL-0c-zSr"/>
</constraints>
</view>
<connections>
<outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
</connections>
<point key="canvasLocation" x="205.5" y="162"/>
</window>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="NO" id="8rY-Kl-bJs">
<rect key="frame" x="0.0" y="0.0" width="38" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Label" id="AAC-Kf-Jrf">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<point key="canvasLocation" x="145" y="385.5"/>
</textField>
</objects>
<resources>
<image name="NSColorPanel" width="32" height="32"/>
<image name="NSStopProgressFreestandingTemplate" width="14" height="14"/>
</resources>
</document>

View File

@ -0,0 +1,24 @@
//
// RulesWindowController.h
// OverSight
//
// Created by Patrick Wardle on 7/7/16.
// Copyright (c) 2016 Objective-See. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface RulesWindowController : NSWindowController <NSWindowDelegate, NSTableViewDataSource, NSTableViewDelegate>
/* PROPERTIES */
@property(nonatomic, retain)NSMutableArray* items;
//table view
@property (weak) IBOutlet NSTableView *tableView;
/* METHODS */
//delete a rule
- (IBAction)deleteRule:(id)sender;
@end

View File

@ -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

View File

@ -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 = "<group>"; };
7DAF4B7C1D656FD3000DA31A /* StatusBarMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusBarMenu.h; sourceTree = "<group>"; };
7DAF4B7D1D656FD3000DA31A /* StatusBarMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusBarMenu.m; sourceTree = "<group>"; };
7DC037FF1E87025100349474 /* RulesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RulesWindowController.h; sourceTree = "<group>"; };
7DC038001E87025100349474 /* RulesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RulesWindowController.m; sourceTree = "<group>"; };
7DC038011E87025100349474 /* Rules.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Rules.xib; sourceTree = "<group>"; };
7DC038041E8716F700349474 /* RuleRowCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuleRowCell.h; sourceTree = "<group>"; };
7DC038051E8716F700349474 /* RuleRowCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuleRowCell.m; sourceTree = "<group>"; };
7DC038071E8717B200349474 /* RuleRow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuleRow.h; sourceTree = "<group>"; };
7DC038081E8717B200349474 /* RuleRow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuleRow.m; sourceTree = "<group>"; };
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 = "<group>"; };
7DC9C8161D641A350017D143 /* OverSightXPC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OverSightXPC.m; sourceTree = "<group>"; };
@ -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;

View File

@ -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]);

View File

@ -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;

View File

@ -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

View File

@ -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 @"<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

View File

@ -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"

View File

@ -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);

View File

@ -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;
//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;
}

View File

@ -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;