428 lines
12 KiB
Objective-C
428 lines
12 KiB
Objective-C
//
|
|
// AppDelegate.m
|
|
// Lockdown
|
|
//
|
|
// Created by Patrick Wardle on 2/14/16 and is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.
|
|
//
|
|
// 3rd-party components (all MIT license'd)
|
|
// a) osxlockdown: https://github.com/SummitRoute/osxlockdown
|
|
// b) ANSIEscapeHelper: https://github.com/ali-rantakari/ANSIEscapeHelper
|
|
// c) YAML -> Obj-C framework: https://github.com/mirek/YAML.framework
|
|
|
|
#import <syslog.h>
|
|
#include <sys/xattr.h>
|
|
|
|
#import "Consts.h"
|
|
#import "Exception.h"
|
|
#import "Utilities.h"
|
|
#import "AppDelegate.h"
|
|
#import "CommandsViewController.h"
|
|
#import "ExecuteViewController.h"
|
|
#import "WarningViewController.h"
|
|
#import "3rd-Party/YAML.framework/Headers/YAMLSerialization.h"
|
|
|
|
|
|
@implementation AppDelegate
|
|
|
|
@synthesize mode;
|
|
@synthesize commands;
|
|
@synthesize commandsFile;
|
|
@synthesize aboutWindowController;
|
|
@synthesize currentViewController;
|
|
@synthesize errorWindowController;
|
|
|
|
|
|
//automatically called as app's 'main' method
|
|
// ->check version, load commands, and initial UI view
|
|
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
|
{
|
|
//app's (self) signing status
|
|
OSStatus signingStatus = !noErr;
|
|
|
|
//install exception handlers
|
|
installExceptionHandlers();
|
|
|
|
//check if OS is supported
|
|
if(YES != isSupportedOS())
|
|
{
|
|
//show error popup
|
|
[self displayErrorWindow: @{KEY_ERROR_MSG:@"ERROR: unsupported OS", KEY_ERROR_SUB_MSG: [NSString stringWithFormat:@"OS X %@ is not supported", [[NSProcessInfo processInfo] operatingSystemVersionString]], KEY_ERROR_SHOULD_EXIT:@YES}];
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//r00t
|
|
// ->for realz
|
|
if(0 != setuid(0))
|
|
{
|
|
//err msg
|
|
syslog(LOG_ERR, "OBJECTIVE-SEE LOCKDOWN ERROR: setuid() failed with: %d\n", errno);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//alloc array for commands
|
|
commands = [NSMutableArray array];
|
|
|
|
//get a path for the commands file
|
|
// ->as user might select/deselect commands
|
|
commandsFile = [NSTemporaryDirectory() stringByAppendingPathComponent:COMMANDS_FILE];
|
|
|
|
//prepare osxlockdown
|
|
// ->set to r00t, remove q attr, etc
|
|
[self prepOSXLockdown];
|
|
|
|
//(re)verify self
|
|
// ->show error if app cannot be verified (will exit)
|
|
signingStatus = verifySelf();
|
|
if(noErr != signingStatus)
|
|
{
|
|
//show alert
|
|
showUnverifiedAlert(signingStatus);
|
|
|
|
//exit
|
|
exit(0);
|
|
}
|
|
|
|
//load yaml commands
|
|
if(YES != [self loadCommands])
|
|
{
|
|
//show error popup
|
|
[self displayErrorWindow: @{KEY_ERROR_MSG:@"ERROR: load failure", KEY_ERROR_SUB_MSG: @"failed to load lockdown commands", KEY_ERROR_SHOULD_EXIT:@YES}];
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//all happy, now enable 'continue' button
|
|
// ->need 'else' to scope for dispatch_after
|
|
else
|
|
{
|
|
//wait 1 second
|
|
// ->then enable 'continue' button & make it selected
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
|
|
|
//enable
|
|
((WarningViewController*)self.currentViewController).continueButton.enabled = YES;
|
|
|
|
//make selected
|
|
[[[NSApplication sharedApplication] keyWindow] makeFirstResponder:((WarningViewController*)self.currentViewController).continueButton];
|
|
});
|
|
}
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return;
|
|
}
|
|
|
|
//tell app to close when user clicks 'x' button
|
|
-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
//delete temp copy of commands file
|
|
-(void)applicationWillTerminate:(NSNotification *)notification
|
|
{
|
|
//delete
|
|
[[NSFileManager defaultManager] removeItemAtPath:self.commandsFile error:NULL];
|
|
|
|
return;
|
|
}
|
|
|
|
//prepare osxlockdown components
|
|
// ->make exec, set to r00t, etc...
|
|
-(void)prepOSXLockdown
|
|
{
|
|
//path to osxlockdown binary
|
|
NSString* osxlockdown = nil;
|
|
|
|
//init path to osxlockdown binary
|
|
osxlockdown = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:BINARY_FILE];
|
|
|
|
//make sure osxlockdown binary has executable bit set
|
|
[[NSFileManager defaultManager] setAttributes:@{NSFilePosixPermissions:@0755} ofItemAtPath:osxlockdown error:NULL];
|
|
|
|
//set osxlockdown binary to r00t
|
|
[[NSFileManager defaultManager] setAttributes:@{NSFileGroupOwnerAccountID:@0, NSFileOwnerAccountID:@0} ofItemAtPath:osxlockdown error:NULL];
|
|
|
|
//set osxlockdown commands to r00t
|
|
[[NSFileManager defaultManager] setAttributes:@{NSFileGroupOwnerAccountID:@0, NSFileOwnerAccountID:@0} ofItemAtPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:COMMANDS_FILE] error:NULL];
|
|
|
|
return;
|
|
}
|
|
|
|
//automatically invoked when user clicks 'About/Info'
|
|
// ->show about window
|
|
-(IBAction)about:(id)sender
|
|
{
|
|
//alloc/init settings window
|
|
if(nil == self.aboutWindowController)
|
|
{
|
|
//alloc/init
|
|
aboutWindowController = [[AboutWindowController alloc] initWithWindowNibName:@"AboutWindow"];
|
|
}
|
|
|
|
//center window
|
|
[[self.aboutWindowController window] center];
|
|
|
|
//show it
|
|
[self.aboutWindowController showWindow:self];
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//change top pane
|
|
// ->switch between either flat (default) or tree-based (hierachical) view
|
|
-(void)changeViewController:(NSUInteger)viewID
|
|
{
|
|
//key window
|
|
NSWindow* keyWindow = nil;
|
|
|
|
//grab key window
|
|
keyWindow = [[NSApplication sharedApplication] keyWindow];
|
|
|
|
//first
|
|
// ->remove existing view
|
|
if([self.currentViewController view] != nil)
|
|
{
|
|
//remove
|
|
[[self.currentViewController view] removeFromSuperview];
|
|
|
|
//'free'
|
|
self.currentViewController = nil;
|
|
}
|
|
|
|
//display specified view
|
|
switch(viewID)
|
|
{
|
|
//warning view
|
|
case VIEW_WARNING:
|
|
{
|
|
//alloc/init
|
|
currentViewController = [[WarningViewController alloc] initWithNibName:@"WarningView" bundle:nil];
|
|
|
|
break;
|
|
}
|
|
|
|
//command selector view
|
|
case VIEW_COMMANDS:
|
|
{
|
|
//alloc/init
|
|
currentViewController = [[CommandsViewController alloc] initWithNibName:@"TestsView" bundle:nil];
|
|
|
|
//resize window
|
|
[keyWindow setFrame:NSMakeRect(0,0,600,533) display:YES];
|
|
|
|
//set view's frame to match window's
|
|
self.currentViewController.view.frame = [keyWindow contentRectForFrameRect:keyWindow.frame];
|
|
|
|
//center window
|
|
[[[NSApplication sharedApplication] keyWindow] center];
|
|
|
|
break;
|
|
}
|
|
|
|
//execute commands view
|
|
case VIEW_EXECUTE:
|
|
{
|
|
//alloc/init
|
|
currentViewController = [[ExecuteViewController alloc] initWithNibName:@"ExecuteView" bundle:nil];
|
|
|
|
//resize window
|
|
[keyWindow setFrame:NSMakeRect(0,0,600,533) display:YES];
|
|
|
|
//set view's frame to match window's
|
|
self.currentViewController.view.frame = [keyWindow contentRectForFrameRect:keyWindow.frame];
|
|
|
|
//center window
|
|
[[[NSApplication sharedApplication] keyWindow] center];
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//add subview
|
|
[self.mainView addSubview:self.currentViewController.view];
|
|
|
|
return;
|
|
}
|
|
|
|
//load original commands
|
|
// ->read them into memory and convery into an objc obj
|
|
-(BOOL)loadCommands
|
|
{
|
|
//loaded
|
|
BOOL bLoaded = NO;
|
|
|
|
//error
|
|
NSError* error = nil;
|
|
|
|
//path to commands
|
|
NSString* commandsPath = nil;
|
|
|
|
//input stream
|
|
NSInputStream *yamlStream = nil;
|
|
|
|
//init path to commands file
|
|
commandsPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:COMMANDS_FILE];
|
|
|
|
//init yaml input stream from w/ commands
|
|
yamlStream = [[NSInputStream alloc] initWithFileAtPath:commandsPath];
|
|
if(nil == yamlStream)
|
|
{
|
|
//err msg
|
|
syslog(LOG_ERR, "OBJECTIVE-SEE LOCKDOWN ERROR: loading commands initialization failed\n");
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//load commands
|
|
self.commands = [YAMLSerialization objectsWithYAMLStream:yamlStream options:kYAMLReadOptionStringScalars error:&error];
|
|
|
|
//make sure loading succeeded and data looks ok
|
|
if( (nil != error) ||
|
|
(YES != [self.commands isKindOfClass:[NSMutableArray class]]) ||
|
|
(0 == self.commands.count) )
|
|
{
|
|
//err msg
|
|
syslog(LOG_ERR, "OBJECTIVE-SEE LOCKDOWN ERROR: loading commands failed with: %s\n", error.description.UTF8String);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//format is an array, with a single member
|
|
// ->and array of dictionaries (want that)
|
|
self.commands = self.commands.firstObject;
|
|
|
|
//again, make sure this data looks ok
|
|
if( (YES != [self.commands isKindOfClass:[NSMutableArray class]]) ||
|
|
(0 == self.commands.count) )
|
|
{
|
|
//err msg
|
|
syslog(LOG_ERR, "OBJECTIVE-SEE LOCKDOWN ERROR: loading commands failed\n");
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//happy
|
|
bLoaded = YES;
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return bLoaded;
|
|
}
|
|
|
|
//save commands
|
|
// ->write commands out to disk
|
|
-(BOOL)saveCommands
|
|
{
|
|
//loaded
|
|
BOOL bSaved = NO;
|
|
|
|
//error
|
|
NSError* error = nil;
|
|
|
|
//output stream
|
|
NSOutputStream *yamlStream = nil;
|
|
|
|
//init output stream
|
|
yamlStream = [NSOutputStream outputStreamToFileAtPath:self.commandsFile append:NO];
|
|
if(nil == yamlStream)
|
|
{
|
|
//err msg
|
|
syslog(LOG_ERR, "OBJECTIVE-SEE LOCKDOWN ERROR: failed to output output stream for %s\n", self.commandsFile.UTF8String);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//open stream
|
|
[yamlStream open];
|
|
|
|
//write commands to stream
|
|
if(YES != [YAMLSerialization writeObject:self.commands toYAMLStream:yamlStream options:kYAMLWriteOptionSingleDocument error:&error])
|
|
{
|
|
//err msg
|
|
syslog(LOG_ERR, "OBJECTIVE-SEE LOCKDOWN ERROR: writing commands to output stream failed with: %s\n", error.description.UTF8String);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//close stream
|
|
[yamlStream close];
|
|
|
|
//happy
|
|
bSaved = YES;
|
|
|
|
//bail
|
|
bail:
|
|
|
|
return bSaved;
|
|
}
|
|
|
|
//display error window
|
|
-(void)displayErrorWindow:(NSDictionary*)errorInfo
|
|
{
|
|
//alloc error window
|
|
errorWindowController = [[ErrorWindowController alloc] initWithWindowNibName:@"ErrorWindowController"];
|
|
|
|
//main thread
|
|
// ->just show UI alert, unless its fatal (then load URL)
|
|
if(YES == [NSThread isMainThread])
|
|
{
|
|
//non-fatal errors
|
|
// ->show error error popup
|
|
if(YES != [errorInfo[KEY_ERROR_URL] isEqualToString:FATAL_ERROR_URL])
|
|
{
|
|
//display it
|
|
// ->call this first to so that outlets are connected
|
|
[self.errorWindowController display];
|
|
|
|
//configure it
|
|
[self.errorWindowController configure:errorInfo];
|
|
}
|
|
//fatal error
|
|
// ->launch browser to go to fatal error page, then exit
|
|
else
|
|
{
|
|
//launch browser
|
|
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:errorInfo[KEY_ERROR_URL]]];
|
|
|
|
//then exit
|
|
[NSApp terminate:self];
|
|
}
|
|
}
|
|
//background thread
|
|
// ->have to show error window on main thread
|
|
else
|
|
{
|
|
//show alert
|
|
// ->in main UI thread
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
|
|
//display it
|
|
// ->call this first to so that outlets are connected
|
|
[self.errorWindowController display];
|
|
|
|
//configure it
|
|
[self.errorWindowController configure:errorInfo];
|
|
|
|
});
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
@end
|