diff --git a/LuLu/App/AddRuleWindowController.m b/LuLu/App/AddRuleWindowController.m index 06c5510..9716079 100755 --- a/LuLu/App/AddRuleWindowController.m +++ b/LuLu/App/AddRuleWindowController.m @@ -312,7 +312,7 @@ -(IBAction)addButtonHandler:(id)sender if(YES != [path hasSuffix:VALUE_ANY]) { //show alert - showAlert(@"ERROR: invalid path", [NSString stringWithFormat:@"%@ does not exist!", path], @[@"OK"]); + showAlert(NSAlertStyleWarning, @"ERROR: invalid path", [NSString stringWithFormat:@"%@ does not exist!", path], @[@"OK"]); //bail goto bail; @@ -330,7 +330,7 @@ -(IBAction)addButtonHandler:(id)sender (nil == [NSRegularExpression regularExpressionWithPattern:endpointAddr options:0 error:&error]) ) { //show alert - showAlert(@"ERROR: invalid regex", [NSString stringWithFormat:@"%@ is not a valid regular expression\r\ndetails: %@", endpointAddr, error.localizedDescription], @[@"OK"]); + showAlert(NSAlertStyleWarning, @"ERROR: invalid regex", [NSString stringWithFormat:@"%@ is not a valid regular expression\r\ndetails: %@", endpointAddr, error.localizedDescription], @[@"OK"]); //bail goto bail; @@ -344,7 +344,7 @@ -(IBAction)addButtonHandler:(id)sender (NSNotFound != [endpointPort rangeOfCharacterFromSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]].location) ) { //show alert - showAlert(@"ERROR: invalid port", [NSString stringWithFormat:@"%@ is not a valid (port) number", endpointPort], @[@"OK"]); + showAlert(NSAlertStyleWarning, @"ERROR: invalid port", [NSString stringWithFormat:@"%@ is not a valid (port) number", endpointPort], @[@"OK"]); //bail goto bail; diff --git a/LuLu/App/AppDelegate.m b/LuLu/App/AppDelegate.m index 3d7beab..18da3b4 100644 --- a/LuLu/App/AppDelegate.m +++ b/LuLu/App/AppDelegate.m @@ -7,8 +7,10 @@ // #import "consts.h" -#import "Update.h" #import "utilities.h" + +#import "Update.h" +#import "Configure.h" #import "Extension.h" #import "AppDelegate.h" @@ -59,7 +61,7 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; //show alert - showAlert([NSString stringWithFormat:@"LuLu must run from:\r\n %@", [@"/Applications" stringByAppendingPathComponent:APP_NAME]], @"...please copy it to /Applications and re-launch.", @[@"OK"]); + showAlert(NSAlertStyleInformational, [NSString stringWithFormat:@"LuLu must run from:\r\n %@", [@"/Applications" stringByAppendingPathComponent:APP_NAME]], @"...please copy it to /Applications and re-launch.", @[@"OK"]); //exit [NSApplication.sharedApplication terminate:self]; @@ -190,7 +192,7 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; //show alert - showAlert(@"ERROR: activation failed", @"failed to activate system/network extension", @[@"OK"]); + showAlert(NSAlertStyleCritical, @"ERROR: activation failed", @"failed to activate system/network extension", @[@"OK"]); //exit [NSApplication.sharedApplication terminate:self]; @@ -214,7 +216,7 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification dispatch_async(dispatch_get_main_queue(), ^{ //show alert - showAlert(@"ERROR: activation failed", @"failed to activate network filter", @[@"OK"]); + showAlert(NSAlertStyleCritical, @"ERROR: activation failed", @"failed to activate network filter", @[@"OK"]); //bye [NSApplication.sharedApplication terminate:self]; @@ -335,7 +337,7 @@ -(BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:( if(YES != [extension isExtensionRunning]) { //show alert - showAlert(@"LuLu's Network Extension Is Not Running", @"Extensions must be manually approved via Security & Privacy System Preferences.", @[@"OK"]); + showAlert(NSAlertStyleInformational, @"LuLu's Network Extension Is Not Running", @"Extensions must be manually approved via Security & Privacy System Preferences.", @[@"OK"]); //bail goto bail; @@ -721,30 +723,15 @@ -(void)check4Update // do any cleanup, then exit -(IBAction)quit:(id)sender { - //(confirmation) alert - NSAlert* alert = nil; + //response + NSModalResponse response = 0; + + //config obj + Configure* configure = nil; //dbg msg - os_log_debug(logHandle, "quitting..."); + os_log_debug(logHandle, "function '%s' invoked", __PRETTY_FUNCTION__); - //init alert - alert = [[NSAlert alloc] init]; - - //set style - alert.alertStyle = NSAlertStyleInformational; - - //main text - alert.messageText = @"Quit LuLu?"; - - //details - alert.informativeText = @"...this will terminated LuLu, until the next time you log in."; - - //add button - [alert addButtonWithTitle:@"Quit"]; - - //add button - [alert addButtonWithTitle:@"Cancel"]; - //foreground [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; @@ -753,10 +740,13 @@ -(IBAction)quit:(id)sender //make app active [NSApp activateIgnoringOtherApps:YES]; - + + //show alert + response = showAlert(NSAlertStyleInformational, @"Quit LuLu?", @"...this will terminated LuLu, until the next time you log in.", @[@"Quit", @"Cancel"]); + //show alert // cancel? ignore - if(NSModalResponseCancel == [alert runModal]) + if(NSModalResponseCancel == response) { //dbg msg os_log_debug(logHandle, "user canceled quitting"); @@ -771,77 +761,14 @@ -(IBAction)quit:(id)sender //dbg msg os_log_debug(logHandle, "user confirmed quit"); - //deactive network extension - // this will also cause the extension to be unloaded - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - - //extension - Extension* extension = nil; - - //wait semaphore - dispatch_semaphore_t semaphore = 0; - - //init extension object - extension = [[Extension alloc] init]; - - //init wait semaphore - semaphore = dispatch_semaphore_create(0); - - //kick off extension deactivation request - [extension toggleExtension:ACTION_DEACTIVATE reply:^(BOOL toggled) - { - //dbg msg - os_log_debug(logHandle, "extension 'deactivate' returned..."); - - //error - // user likely cancelled - if(YES != toggled) - { - //err msg - os_log_error(logHandle, "ERROR: failed to deactivate extension (won't quit)"); - } - //happy - else - { - //terminate network monitor - [self terminateNetworkMonitor]; - - //dbg msg - os_log_debug(logHandle, "all done, goodbye!"); - - //bye - [NSApplication.sharedApplication terminate:self]; - } - - //signal semaphore - dispatch_semaphore_signal(semaphore); - - }]; - - //wait for extension semaphore - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); - - }); - } - - return; -} - -//terminate network monitor --(void)terminateNetworkMonitor -{ - //find match - // will check if LuLu's, then will terminate - for(NSRunningApplication* networkMonitor in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.objective-see.Netiquette"]) - { - //non LuLu instance? - if(YES != [networkMonitor.bundleURL.path hasPrefix:NSBundle.mainBundle.resourcePath]) continue; + //init + configure = [[Configure alloc] init]; - //dbg msg - os_log_debug(logHandle, "terminating network monitor: %{public}@", networkMonitor); + //quit + [configure quit]; - //terminate - [networkMonitor terminate]; + //and terminate + [NSApplication.sharedApplication terminate:self]; } return; @@ -851,26 +778,11 @@ -(void)terminateNetworkMonitor // cleanup all the thingz! -(IBAction)uninstall:(id)sender { - //(confirmation) alert - NSAlert* alert = nil; - - //init alert - alert = [[NSAlert alloc] init]; - - //set style - alert.alertStyle = NSAlertStyleInformational; - - //main text - alert.messageText = @"Uninstall LuLu?"; + //response + NSModalResponse response = 0; - //details - alert.informativeText = @"...this will fully remove LuLu from your Mac!"; - - //add button - [alert addButtonWithTitle:@"Uninstall"]; - - //add button - [alert addButtonWithTitle:@"Cancel"]; + //config obj + Configure* configure = nil; //foreground [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; @@ -880,128 +792,45 @@ -(IBAction)uninstall:(id)sender //make key and front [self.window makeKeyAndOrderFront:self]; - + //show alert - // and if not a 'cancel', uninstall - if(NSModalResponseCancel != [alert runModal]) + response = showAlert(NSAlertStyleInformational, @"Uninstall LuLu?", @"...this will fully remove LuLu from your Mac", @[@"Uninstall", @"Cancel"]); + + //show alert + // cancel? ignore + if(NSModalResponseCancel == response) { - //dbg msg - os_log_debug(logHandle, "user confirmed uninstall"); - - //tell extension to uninstall - // and then deactive network extension - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - - //extension - Extension* extension = nil; - - //flag - __block BOOL deactivated = NO; - - //wait semaphore - dispatch_semaphore_t semaphore = 0; - - //error - NSError* error = nil; - - //app path - NSString* path = nil; - - //init extension object - extension = [[Extension alloc] init]; - - //init wait semaphore - semaphore = dispatch_semaphore_create(0); - - //tell ext to uninstall - // remove rules, etc, etc - if(YES != [xpcDaemonClient uninstall]) - { - //err msg - os_log_error(logHandle, "ERROR: daemon's XPC uninstall logic"); - - //but continue onwards - } - - //user has to remove - // otherwise we get into a funky state :/ - while(YES) - { - //kick off extension activation request - [extension toggleExtension:ACTION_DEACTIVATE reply:^(BOOL toggled) - { - //save - deactivated = toggled; - - //toggled ok? - if(YES == toggled) - { - //dbg msg - os_log_debug(logHandle, "extension deactivated"); - } - //failed? - else - { - //err msg - os_log_error(logHandle, "ERROR: failed to deactivate extension, will reattempt"); - } - - //signal semaphore - dispatch_semaphore_signal(semaphore); - }]; - - //dbg msg - os_log_debug(logHandle, "waiting system extension deactivation..."); - - //wait for extension semaphore - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); - - //dbg msg - os_log_debug(logHandle, "extension event triggered"); - - //deactivated? - if(YES == deactivated) break; - } - - //remove login item - if(YES != toggleLoginItem(NSBundle.mainBundle.bundleURL, ACTION_UNINSTALL_FLAG)) - { - //err msg - os_log_error(logHandle, "ERROR: failed to uninstall login item"); - - } else os_log_debug(logHandle, "uninstalled login item"); - - //init app path - path = NSBundle.mainBundle.bundlePath; - - //remove app - if(YES != [NSFileManager.defaultManager removeItemAtPath:path error:&error]) - { - //err msg - os_log_error(logHandle, "ERROR: failed to remove %{public}@ (error: %{public}@)", path, error); - - } else os_log_debug(logHandle, "removed %{public}@", path); - - //terminate network monitor - [self terminateNetworkMonitor]; - - //exit - [NSApplication.sharedApplication terminate:self]; - - }); + //dbg msg + os_log_debug(logHandle, "user canceled uninstalling"); + + //(re)background + [self setActivationPolicy]; } + //ok + // user wants to uninstall! else { //dbg msg - os_log_debug(logHandle, "user canceled uninstall"); - - //(re)background - [self setActivationPolicy]; - } + os_log_debug(logHandle, "user confirmed uninstall"); + //init + configure = [[Configure alloc] init]; + + //quit + if(YES != [configure uninstall]) + { + //err msg + os_log_error(logHandle, "ERROR: uninstall failed"); + } + + //and terminate + [NSApplication.sharedApplication terminate:self]; + } + bail: return; } + @end diff --git a/LuLu/App/Configure.h b/LuLu/App/Configure.h new file mode 100644 index 0000000..82b9351 --- /dev/null +++ b/LuLu/App/Configure.h @@ -0,0 +1,30 @@ +// +// Configure.h +// LuLu +// +// Created by Patrick Wardle on 2/6/24. +// Copyright © 2024 Objective-See. All rights reserved. +// + +#ifndef Configure_h +#define Configure_h + +@import Foundation; + +@interface Configure : NSObject + +//quit +-(void)quit; + +//install +-(BOOL)install; + +//upgrade +-(BOOL)upgrade; + +//uninstall +-(BOOL)uninstall; + +@end + +#endif /* Configure_h */ diff --git a/LuLu/App/Configure.m b/LuLu/App/Configure.m new file mode 100644 index 0000000..e48a18e --- /dev/null +++ b/LuLu/App/Configure.m @@ -0,0 +1,389 @@ +// +// Configure.m +// LuLu +// +// Created by Patrick Wardle on 2/6/24. +// Copyright © 2024 Objective-See. All rights reserved. +// + +#import "consts.h" +#import "utilities.h" + +#import "Configure.h" +#import "Extension.h" +#import "XPCDaemonClient.h" + +/* GLOBALS */ + +//log handle +extern os_log_t logHandle; + +//xpc for daemon comms +extern XPCDaemonClient* xpcDaemonClient; + +@implementation Configure + +//init +-(id)init +{ + //init + if(self = [super init]) + { + //if needed, in extension comms + if(nil == xpcDaemonClient) + { + //init + xpcDaemonClient = [[XPCDaemonClient alloc] init]; + } + } + + return self; +} + +//install +-(BOOL)install +{ + //flag + BOOL installed = NO; + + //error + NSError* error = nil; + + //source + NSString* source = nil; + + //destination + NSString* destination = nil; + + //dbg msg + os_log_debug(logHandle, "function '%s' invoked", __PRETTY_FUNCTION__); + + //quit LuLu + [self quit]; + + //init source + source = NSBundle.mainBundle.bundlePath; + + //init destination + destination = [@"/Applications" stringByAppendingPathComponent:APP_NAME]; + + //remove any existing LuLu.app + if(YES == [NSFileManager.defaultManager fileExistsAtPath:destination]) + { + //not us? + // remove + if(YES != [source isEqualToString:destination]) + { + //remove + if(YES != [NSFileManager.defaultManager removeItemAtPath:destination error:&error]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to remove %{public}@ (error: %{public}@)", destination, error); + goto bail; + } + } + } + + //copy self into /Applications + if(YES != [NSFileManager.defaultManager copyItemAtPath:source toPath:destination error:&error]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to move %{public}@ to %{public}@ (error: %{public}@)", source, destination, error); + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "moved self into /Applications and will launch..."); + + //now launch + if(nil == [NSWorkspace.sharedWorkspace launchApplicationAtURL:[NSURL fileURLWithPath:destination] options:0 configuration:@{} error:&error]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to launch %{public}@, (error: %{public}@)", destination, error); + goto bail; + } + + //happy + installed = YES; + +bail: + + return installed; + +} + +//upgrade +// same as install +-(BOOL)upgrade +{ + //dbg msg + os_log_debug(logHandle, "function '%s' invoked", __PRETTY_FUNCTION__); + + return [self install]; +} + +//unistall +-(BOOL)uninstall +{ + //flag + BOOL errors = NO; + + //error + NSError* error = nil; + + //app + NSString* app = [@"/Applications" stringByAppendingPathComponent:APP_NAME]; + + //dbg msg + os_log_debug(logHandle, "function '%s' invoked", __PRETTY_FUNCTION__); + + //tell ext. to uninstall + // remove rules, etc, etc + if(YES != [xpcDaemonClient uninstall]) + { + //err msg + os_log_error(logHandle, "ERROR: daemon's XPC uninstall logic"); + + //set flag + errors = YES; + + //but continue onwards + } + + //first, remove login item + toggleLoginItem([NSURL fileURLWithPath:app], ACTION_UNINSTALL_FLAG); + + //quit (other) LuLu + [self quit]; + + //app found in /Apps? + if(YES == [NSFileManager.defaultManager fileExistsAtPath:app]) + { + //remove + if(YES != [NSFileManager.defaultManager removeItemAtPath:app error:&error]) + { + //set flag + errors = YES; + + //err msg + os_log_error(logHandle, "ERROR: failed to remove %{public}@ (error: %{public}@)", app, error); + + //but continue onwards + + } else os_log_debug(logHandle, "removed %{public}@", app); + } + + //dbg msg + os_log_debug(logHandle, "uninstalling completed (with any errors? %d)", errors); + + return !errors; +} + +//quit +// and optionally uninstall +-(void)quit +{ + //extension + Extension* extension = nil; + + //source + NSString* source = nil; + + //copy in /Apps + NSString* copy = nil; + + //running copy + NSRunningApplication* runningCopy = nil; + + //error + NSError* error = nil; + + //flag + __block BOOL deactivated = NO; + + //wait semaphore + dispatch_semaphore_t semaphore = 0; + + //dbg msg + os_log_debug(logHandle, "function '%s' invoked", __PRETTY_FUNCTION__); + + //init extension object + extension = [[Extension alloc] init]; + + //init source + source = NSBundle.mainBundle.bundlePath; + + //init copy + copy = [@"/Applications" stringByAppendingPathComponent:APP_NAME]; + + //end LuLu + // besides this running instance + for(NSDictionary* lulu in findProcesses(@"LuLu")) + { + //pid + NSNumber* pid = 0; + + //extract pid + pid = lulu[KEY_PROCESS_ID]; + + //skip self + if(pid.intValue == getpid()) + { + //skip + continue; + } + + //dbg msg + os_log_debug(logHandle, "terminating %{public}@", lulu); + + //kill + kill(pid.intValue, SIGKILL); + } + + //terminate NQ + [self terminateNetworkMonitor]; + + //need to stop extension? + if(YES == [extension isExtensionRunning]) + { + //dbg msg + os_log_debug(logHandle, "extension running, will deactivate..."); + + //have to be running from /Applications for this to work + // so if we're not there, spawn a copy to exectute this logic + if(YES != [source isEqualToString:copy]) + { + //dbg msg + os_log_debug(logHandle, "will spawn copy from /Applications"); + + //any existing? + if(YES == [NSFileManager.defaultManager fileExistsAtPath:copy]) + { + //remove + if(YES != [NSFileManager.defaultManager removeItemAtPath:copy error:&error]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to remove %{public}@ (error: %{public}@)", copy, error); + goto bail; + } + } + + //copy self into /Applications + if(YES != [NSFileManager.defaultManager copyItemAtPath:source toPath:copy error:&error]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to move %{public}@ to %{public}@ (error: %{public}@)", source, copy, error); + goto bail; + } + + //dbg msg + os_log_debug(logHandle, "launching copy %{public}@, to deactivate extension", copy); + + //launch copy + runningCopy = [NSWorkspace.sharedWorkspace launchApplicationAtURL:[NSURL fileURLWithPath:copy] options:0 configuration:[NSDictionary dictionaryWithObject:@[@"-quit"] forKey:NSWorkspaceLaunchConfigurationArguments] error:&error]; + if(nil == runningCopy) + { + //err msg + os_log_error(logHandle, "ERROR: failed to launch copy, %{public}@, (error: %{public}@)", copy, error); + goto bail; + } + + //wait till copy exits + while(YES != runningCopy.isTerminated) + { + [NSThread sleepForTimeInterval:0.1]; + } + + //dbg msg + os_log_debug(logHandle, "copy terminated, will delete"); + + //delete copy + if(YES != [NSFileManager.defaultManager removeItemAtPath:copy error:&error]) + { + //err msg + os_log_error(logHandle, "ERROR: failed to remove %{public}@ (error: %{public}@)", copy, error); + goto bail; + + } + + //dbg msg + os_log_debug(logHandle, "removed copy %{public}@", copy); + } + + //(now) running from /Apps + // go ahead and remove extension + else + { + //init wait semaphore + semaphore = dispatch_semaphore_create(0); + + //user has to remove + // otherwise we get into a funky state :/ + while(YES) + { + //kick off extension activation request + [extension toggleExtension:ACTION_DEACTIVATE reply:^(BOOL toggled) + { + //save + deactivated = toggled; + + //toggled ok? + if(YES == toggled) + { + //dbg msg + os_log_debug(logHandle, "extension deactivated"); + } + //failed? + else + { + //err msg + os_log_error(logHandle, "ERROR: failed to deactivate extension, will reattempt"); + } + + //signal semaphore + dispatch_semaphore_signal(semaphore); + }]; + + //dbg msg + os_log_debug(logHandle, "waiting system extension deactivation..."); + + //wait for extension semaphore + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + + //dbg msg + os_log_debug(logHandle, "extension event triggered"); + + //deactivated? + if(YES == deactivated) break; + } + } + } + +bail: + + return; +} + +//terminate network monitor +// unless its the non-LuLu version +-(void)terminateNetworkMonitor +{ + //find match + // will check if LuLu's, then will terminate + for(NSRunningApplication* networkMonitor in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.objective-see.Netiquette"]) + { + //non LuLu instance? + if(YES != [networkMonitor.bundleURL.path hasPrefix:NSBundle.mainBundle.resourcePath]) continue; + + //dbg msg + os_log_debug(logHandle, "terminating network monitor: %{public}@", networkMonitor); + + //terminate + [networkMonitor terminate]; + } + + return; +} + +@end + diff --git a/LuLu/App/Extension.m b/LuLu/App/Extension.m index d3b167e..7429a0a 100644 --- a/LuLu/App/Extension.m +++ b/LuLu/App/Extension.m @@ -80,7 +80,7 @@ -(void)toggleExtension:(NSUInteger)action reply:(void (^)(BOOL))reply //check if extension is running -(BOOL)isExtensionRunning { - return (-1 != (findProcess(EXT_BUNDLE_ID))); + return !![findProcesses(EXT_BUNDLE_ID) count]; } //get network extension's status @@ -294,7 +294,7 @@ -(void)requestNeedsUserApproval:(nonnull OSSystemExtensionRequest *)request { if(YES != [((AppDelegate*)[[NSApplication sharedApplication] delegate]) isFirstTime]) { //show alert - showAlert(@"LuLu's Network Extension Is Not Running", @"Extensions must be manually approved via\r\nSecurity & Privacy System Preferences.", @[@"OK"]); + showAlert(NSAlertStyleInformational, @"LuLu's Network Extension Is Not Running", @"Extensions must be manually approved via\r\nSecurity & Privacy System Preferences.", @[@"OK"]); } }); } diff --git a/LuLu/App/PrefsWindowController.m b/LuLu/App/PrefsWindowController.m index 89bcec5..3e4ef7a 100644 --- a/LuLu/App/PrefsWindowController.m +++ b/LuLu/App/PrefsWindowController.m @@ -249,7 +249,7 @@ -(IBAction)togglePreference:(id)sender if(NSControlStateValueOn == state.longValue) { //show alert - showAlert(@"Outgoing traffic will now be blocked.", @"Note however:\r\n▪ Existing connections will not be impacted.\r\n▪ OS traffic (not routed thru LuLu) will not be blocked.", @[@"OK"]); + showAlert(NSAlertStyleInformational, @"Outgoing traffic will now be blocked.", @"Note however:\r\n▪ Existing connections will not be impacted.\r\n▪ OS traffic (not routed thru LuLu) will not be blocked.", @[@"OK"]); } break; diff --git a/LuLu/App/RulesMenuController.m b/LuLu/App/RulesMenuController.m index a61706a..67010ea 100644 --- a/LuLu/App/RulesMenuController.m +++ b/LuLu/App/RulesMenuController.m @@ -167,7 +167,7 @@ -(void)exportRules os_log_error(logHandle, "ERROR: failed to save rules: %{public}@", error); //show alert - showAlert(@"ERROR: Failed to export rules", @"See log for (more) details", @[@"OK"]); + showAlert(NSAlertStyleWarning, @"ERROR: Failed to export rules", @"See log for (more) details", @[@"OK"]); } } }]; @@ -343,7 +343,7 @@ -(BOOL)importRules [[NSNotificationCenter defaultCenter] postNotificationName:RULES_CHANGED object:nil userInfo:nil]; //show alert - showAlert([NSString stringWithFormat:@"Imported %ld rules", count], nil, @[@"OK"]); + showAlert(NSAlertStyleInformational, [NSString stringWithFormat:@"Imported %ld rules", count], nil, @[@"OK"]); bail: @@ -367,7 +367,7 @@ -(BOOL)cleanupRules //show alert // if user cancels, just bail - if(NSAlertSecondButtonReturn == showAlert(@"Cleanup rules that reference (now) deleted items?", nil, @[@"OK", @"Cancel"])) + if(NSAlertSecondButtonReturn == showAlert(NSAlertStyleInformational, @"Cleanup rules that reference (now) deleted items?", nil, @[@"OK", @"Cancel"])) { //dbg msg os_log_debug(logHandle, "user cancelled rule cleanup"); @@ -389,7 +389,7 @@ -(BOOL)cleanupRules [[NSNotificationCenter defaultCenter] postNotificationName:RULES_CHANGED object:nil userInfo:nil]; //share results w/ user - showAlert([NSString stringWithFormat:@"Cleaned up %ld rules", cleanedUpRules], nil, @[@"OK"]); + showAlert(NSAlertStyleInformational, [NSString stringWithFormat:@"Cleaned up %ld rules", cleanedUpRules], nil, @[@"OK"]); bail: diff --git a/LuLu/App/StatusBarItem.m b/LuLu/App/StatusBarItem.m index ff414d7..8573210 100644 --- a/LuLu/App/StatusBarItem.m +++ b/LuLu/App/StatusBarItem.m @@ -286,7 +286,7 @@ -(void)handler:(id)sender if(YES != [self.rulesMenuController importRules]) { //show alert - showAlert(@"ERROR: Failed to import rules", @"See log for (more) details", @[@"OK"]); + showAlert(NSAlertStyleWarning, @"ERROR: Failed to import rules", @"See log for (more) details", @[@"OK"]); //bail goto bail; @@ -304,7 +304,7 @@ -(void)handler:(id)sender if(YES != [self.rulesMenuController cleanupRules]) { //show alert - showAlert(@"ERROR: Failed to cleanup rules", @"See log for (more) details", @[@"OK"]); + showAlert(NSAlertStyleWarning, @"ERROR: Failed to cleanup rules", @"See log for (more) details", @[@"OK"]); //bail goto bail; @@ -334,7 +334,7 @@ -(void)handler:(id)sender //launch // with args - if(nil != [NSWorkspace.sharedWorkspace launchApplicationAtURL:path options:0 configuration:[NSDictionary dictionaryWithObject:@[@"-lulu"] forKey:NSWorkspaceLaunchConfigurationArguments] error:&error]) + if(nil == [NSWorkspace.sharedWorkspace launchApplicationAtURL:path options:0 configuration:[NSDictionary dictionaryWithObject:@[@"-lulu"] forKey:NSWorkspaceLaunchConfigurationArguments] error:&error]) { //err msg os_log_error(logHandle, "ERROR: failed to launch network monitor, %{public}@, (error: %{public}@)", path, error); diff --git a/LuLu/App/WelcomeWindowController.m b/LuLu/App/WelcomeWindowController.m index 9306cbc..bba1eee 100644 --- a/LuLu/App/WelcomeWindowController.m +++ b/LuLu/App/WelcomeWindowController.m @@ -145,7 +145,7 @@ -(IBAction)buttonHandler:(id)sender { dispatch_async(dispatch_get_main_queue(), ^{ //show alert - showAlert(@"ERROR: activation failed", @"failed to activate system extension", @[@"OK"]); + showAlert(NSAlertStyleCritical, @"ERROR: activation failed", @"failed to activate system extension", @[@"OK"]); //bye [NSApplication.sharedApplication terminate:self]; @@ -172,7 +172,7 @@ -(IBAction)buttonHandler:(id)sender { dispatch_async(dispatch_get_main_queue(), ^{ //show alert - showAlert(@"ERROR: activation failed", @"failed to activate network extension", @[@"OK"]); + showAlert(NSAlertStyleCritical, @"ERROR: activation failed", @"failed to activate network extension", @[@"OK"]); //bye [NSApplication.sharedApplication terminate:self]; diff --git a/LuLu/App/XPCUser.m b/LuLu/App/XPCUser.m index fdcc10c..a36b209 100755 --- a/LuLu/App/XPCUser.m +++ b/LuLu/App/XPCUser.m @@ -89,7 +89,7 @@ -(void)alertShow:(NSDictionary*)alert reply:(void (^)(NSDictionary*))reply dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //alert - showAlert([NSString stringWithFormat:@"%@'s code signing information has changed", alert[KEY_PROCESS_NAME]], @"", @[@"OK"]); + showAlert(NSAlertStyleInformational, [NSString stringWithFormat:@"%@'s code signing information has changed", alert[KEY_PROCESS_NAME]], @"", @[@"OK"]); }); } diff --git a/LuLu/App/main.m b/LuLu/App/main.m index 959877d..8c5ae67 100644 --- a/LuLu/App/main.m +++ b/LuLu/App/main.m @@ -8,6 +8,7 @@ #import "consts.h" #import "utilities.h" +#import "Configure.h" @import Cocoa; @import OSLog; @@ -20,6 +21,12 @@ int main(int argc, const char * argv[]) { + //status + int status = -1; + + //config obj + Configure* configure = nil; + //pool @autoreleasepool { @@ -29,8 +36,136 @@ int main(int argc, const char * argv[]) { //dbg msg(s) os_log_debug(logHandle, "started: %{public}@ (pid: %d / uid: %d)", NSProcessInfo.processInfo.arguments.firstObject, getpid(), getuid()); os_log_debug(logHandle, "arguments: %{public}@", NSProcessInfo.processInfo.arguments); - } - //main app interface - return NSApplicationMain(argc, argv); + /* cmdline interface - for install/upgrade/uninstall */ + + //install? + if(YES == [NSProcessInfo.processInfo.arguments containsObject:@"-install"]) + { + //first check root + if(0 != geteuid()) + { + //err msg + printf("\nLULU ERROR: cmdline interface actions require root\n\n"); + goto bail; + } + + //init + configure = [[Configure alloc] init]; + + //dbg msg + os_log_debug(logHandle, "performing cmdline install"); + + //install + if(YES != [configure install]) + { + //error + printf("\nLULU ERROR: install failed (see system log for details)\n\n"); + goto bail; + } + + //dbg msg + printf("\nLULU: installed\n\n"); + + //done + goto bail; + } + + //upgrade? + else if(YES == [NSProcessInfo.processInfo.arguments containsObject:@"-upgrade"]) + { + //first check root + if(0 != geteuid()) + { + //err msg + printf("\nLULU ERROR: cmdline interface actions require root\n\n"); + goto bail; + } + + //init + configure = [[Configure alloc] init]; + + //dbg msg + os_log_debug(logHandle, "performing cmdline upgrade"); + + //upgrade + if(YES != [configure upgrade]) + { + //error + printf("\nLULU ERROR: upgrade failed (see system log for details)\n\n"); + goto bail; + } + + //dbg msg + printf("\nLULU: upgraded\n\n"); + + //done + goto bail; + } + + //uninstall? + if(YES == [NSProcessInfo.processInfo.arguments containsObject:@"-uninstall"]) + { + //first check root + if(0 != geteuid()) + { + //err msg + printf("\nLULU ERROR: cmdline interface actions require root\n\n"); + goto bail; + } + + //init + configure = [[Configure alloc] init]; + + //dbg msg + os_log_debug(logHandle, "performing cmdline uninstall"); + + //uninstall + if(YES != [configure uninstall]) + { + //error + printf("\nLULU ERROR: uninstall failed (see system log for details)\n\n"); + goto bail; + } + + //dbg msg + printf("\nLULU: uninstalled\n\n"); + + //done + goto bail; + } + + //quit? + // this is the copy, to (just) deactivate extension + if(YES == [NSProcessInfo.processInfo.arguments containsObject:@"-quit"]) + { + //init + configure = [[Configure alloc] init]; + + //dbg msg + os_log_debug(logHandle, "performing cmdline quit"); + + //quit + [configure quit]; + + //done + goto bail; + } + + //invalid args + // just print msg, for cmdline case + else if(NSProcessInfo.processInfo.arguments.count > 1) + { + //err msg + printf("\nLULU ERROR: %s are not valid args\n\n", NSProcessInfo.processInfo.arguments.description.UTF8String); + } + + //main app interface + status = NSApplicationMain(argc, argv); + + } //pool + +bail: + + return status; } diff --git a/LuLu/Extension/Rules.m b/LuLu/Extension/Rules.m index dcbce5e..2886c29 100644 --- a/LuLu/Extension/Rules.m +++ b/LuLu/Extension/Rules.m @@ -1189,6 +1189,14 @@ -(NSInteger)cleanup //dbg msg os_log_debug(logHandle, "checking if %{public}@ was deleted", rule.path); + //skip global rules + // ...these don't have paths + if(rule.isGlobal) + { + //skip + continue; + } + //path ...gone? if(YES != [NSFileManager.defaultManager fileExistsAtPath:rule.path]) { diff --git a/LuLu/LuLu.xcodeproj/project.pbxproj b/LuLu/LuLu.xcodeproj/project.pbxproj index cec4164..0c19043 100644 --- a/LuLu/LuLu.xcodeproj/project.pbxproj +++ b/LuLu/LuLu.xcodeproj/project.pbxproj @@ -63,6 +63,7 @@ CDB2CC3924D61B3900D0EECE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CDB2CC3824D61B3900D0EECE /* main.m */; }; CDB2CC3E24D61B3900D0EECE /* com.objective-see.lulu.extension.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = CDB2CC3024D61B3900D0EECE /* com.objective-see.lulu.extension.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; CDB2CC4424DBE48100D0EECE /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDB2CC3224D61B3900D0EECE /* NetworkExtension.framework */; }; + CDB909EB2B72BC230043FEB4 /* Configure.m in Sources */ = {isa = PBXBuildFile; fileRef = CDB909EA2B72BC230043FEB4 /* Configure.m */; }; CDC378C7250C66C300314064 /* Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = CDC378C6250C66C300314064 /* Extension.m */; }; CDC378CB250C83F200314064 /* Netiquette.app in Resources */ = {isa = PBXBuildFile; fileRef = CDC378C9250C83F100314064 /* Netiquette.app */; }; CDC41C412503424800CB302B /* OrderedDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = CDC41C3F2503424800CB302B /* OrderedDictionary.m */; }; @@ -200,6 +201,8 @@ CDB2CC3824D61B3900D0EECE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; CDB2CC3A24D61B3900D0EECE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CDB2CC3B24D61B3900D0EECE /* Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Extension.entitlements; sourceTree = ""; }; + CDB909EA2B72BC230043FEB4 /* Configure.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Configure.m; sourceTree = ""; }; + CDB909EC2B72BC2E0043FEB4 /* Configure.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Configure.h; sourceTree = ""; }; CDC378C5250C66C200314064 /* Extension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Extension.h; sourceTree = ""; }; CDC378C6250C66C300314064 /* Extension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Extension.m; sourceTree = ""; }; CDC378C9250C83F100314064 /* Netiquette.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = Netiquette.app; sourceTree = ""; }; @@ -301,6 +304,8 @@ CDB2CC1C24D61A4E00D0EECE /* AppDelegate.m */, CDB2CC1E24D61A5000D0EECE /* Assets.xcassets */, CDC378C8250C83F100314064 /* Binaries */, + CDB909EC2B72BC2E0043FEB4 /* Configure.h */, + CDB909EA2B72BC230043FEB4 /* Configure.m */, CDC378C5250C66C200314064 /* Extension.h */, CDC378C6250C66C300314064 /* Extension.m */, CDB2CC2324D61A5000D0EECE /* Info.plist */, @@ -569,6 +574,7 @@ CDA136C724F0D7C3005AD424 /* utilities.m in Sources */, CDB2CC2524D61A5000D0EECE /* main.m in Sources */, CD9E147D25171BF200DA05C3 /* ItemPathsWindowController.m in Sources */, + CDB909EB2B72BC230043FEB4 /* Configure.m in Sources */, CDB2CC1D24D61A4E00D0EECE /* AppDelegate.m in Sources */, CD9F567F252533CE00F3DEB5 /* NSApplicationKeyEvents.m in Sources */, CDC41C412503424800CB302B /* OrderedDictionary.m in Sources */, diff --git a/LuLu/Shared/utilities.h b/LuLu/Shared/utilities.h index 77601cf..81712f7 100644 --- a/LuLu/Shared/utilities.h +++ b/LuLu/Shared/utilities.h @@ -65,8 +65,8 @@ NSImage* getIconForProcess(NSString* path); // then make it modal void makeModal(NSWindowController* windowController); -//find a process by name -pid_t findProcess(NSString* processName); +//find all processes by name +NSMutableArray* findProcesses(NSString* processName); //hash a file (sha256) NSMutableString* hashFile(NSString* filePath); @@ -88,7 +88,7 @@ NSString* valueForStringItem(NSString* item); NSDate* dateAdded(NSString* file); //show an alert -NSModalResponse showAlert(NSString* messageText, NSString* informativeText, NSArray* buttons); +NSModalResponse showAlert(NSAlertStyle style, NSString* messageText, NSString* informativeText, NSArray* buttons); //get audit token for pid NSData* tokenForPid(pid_t pid); diff --git a/LuLu/Shared/utilities.m b/LuLu/Shared/utilities.m index ca6c636..fd41999 100644 --- a/LuLu/Shared/utilities.m +++ b/LuLu/Shared/utilities.m @@ -673,15 +673,15 @@ void makeModal(NSWindowController* windowController) return; } -//find a process by name -pid_t findProcess(NSString* processName) +//find all processes by name +NSMutableArray* findProcesses(NSString* processName) { - //pid - pid_t pid = -1; - //status int status = -1; + //pids + NSMutableArray* processes = nil; + //# of procs int numberOfProcesses = 0; @@ -691,6 +691,9 @@ pid_t findProcess(NSString* processName) //process path NSString* processPath = nil; + //init + processes = [NSMutableArray array]; + //get # of procs numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0); if(-1 == numberOfProcesses) @@ -738,10 +741,7 @@ pid_t findProcess(NSString* processName) } //save - pid = pids[i]; - - //pau - break; + [processes addObject:@{KEY_PROCESS_ID:[NSNumber numberWithInt:pids[i]], KEY_PATH:processPath}]; }//all procs @@ -755,7 +755,7 @@ pid_t findProcess(NSString* processName) pids = NULL; } - return pid; + return processes; } //for login item enable/disable @@ -1105,7 +1105,7 @@ BOOL isDarkMode(void) } //show an alert -NSModalResponse showAlert(NSString* messageText, NSString* informativeText, NSArray* buttons) +NSModalResponse showAlert(NSAlertStyle style, NSString* messageText, NSString* informativeText, NSArray* buttons) { //alert NSAlert* alert = nil; @@ -1117,7 +1117,7 @@ NSModalResponse showAlert(NSString* messageText, NSString* informativeText, NSAr alert = [[NSAlert alloc] init]; //set style - alert.alertStyle = NSAlertStyleWarning; + alert.alertStyle = style; //main text alert.messageText = messageText; @@ -1424,10 +1424,11 @@ BOOL launchedByUser(void) //get parent parent = getRealParent(getpid()); - //parent dock or finder? + //parent dock/finder/terminal // ...then assume its user launched if( (YES == [parent[@"CFBundleIdentifier"] isEqualTo:@"com.apple.dock"]) || - (YES == [parent[@"CFBundleIdentifier"] isEqualTo:@"com.apple.finder"]) ) + (YES == [parent[@"CFBundleIdentifier"] isEqualTo:@"com.apple.finder"]) || + (YES == [parent[@"CFBundleIdentifier"] isEqualTo:@"com.apple.Terminal"]) ) { //set flag byUser = YES;