1088 lines
26 KiB
Objective-C
Executable File
1088 lines
26 KiB
Objective-C
Executable File
//
|
|
// file: Utilities.m
|
|
// project: DND (shared)
|
|
// description: various helper/utility functions
|
|
//
|
|
// created by Patrick Wardle
|
|
// copyright (c) 2018 Objective-See. All rights reserved.
|
|
//
|
|
|
|
#import "Consts.h"
|
|
#import "Logging.h"
|
|
#import "Utilities.h"
|
|
|
|
#import <signal.h>
|
|
#import <unistd.h>
|
|
#import <libproc.h>
|
|
#import <sys/stat.h>
|
|
#import <arpa/inet.h>
|
|
#import <sys/socket.h>
|
|
#import <sys/sysctl.h>
|
|
#import <Security/Security.h>
|
|
#import <CommonCrypto/CommonDigest.h>
|
|
#import <LocalAuthentication/LocalAuthentication.h>
|
|
#import <SystemConfiguration/SystemConfiguration.h>
|
|
|
|
//init crash reporting
|
|
void initCrashReporting()
|
|
{
|
|
//sentry
|
|
NSBundle *sentry = nil;
|
|
|
|
//error
|
|
NSError* error = nil;
|
|
|
|
//class
|
|
Class SentryClient = nil;
|
|
|
|
//load senty
|
|
sentry = loadFramework(@"Sentry.framework");
|
|
if(nil == sentry)
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, @"failed to load 'Sentry' framework");
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//get client class
|
|
SentryClient = NSClassFromString(@"SentryClient");
|
|
if(nil == SentryClient)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//set shared client
|
|
[SentryClient setSharedClient:[[SentryClient alloc] initWithDsn:CRASH_REPORTING_URL didFailWithError:&error]];
|
|
if(nil != error)
|
|
{
|
|
//log error
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"initializing 'Sentry' failed with %@", error]);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//start crash handler
|
|
[[SentryClient sharedClient] startCrashHandlerWithError:&error];
|
|
if(nil != error)
|
|
{
|
|
//log error
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"starting 'Sentry' crash handler failed with %@", error]);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
bail:
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//loads a framework
|
|
// note: assumes it is in 'Framework' dir
|
|
NSBundle* loadFramework(NSString* name)
|
|
{
|
|
//handle
|
|
NSBundle* framework = nil;
|
|
|
|
//framework path
|
|
NSString* path = nil;
|
|
|
|
//init path
|
|
path = [NSString stringWithFormat:@"%@/../Frameworks/%@", [NSProcessInfo.processInfo.arguments[0] stringByDeletingLastPathComponent], name];
|
|
|
|
//standardize path
|
|
path = [path stringByStandardizingPath];
|
|
|
|
//init framework (bundle)
|
|
framework = [NSBundle bundleWithPath:path];
|
|
if(NULL == framework)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//load framework
|
|
if(YES != [framework loadAndReturnError:nil])
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
bail:
|
|
|
|
return framework;
|
|
}
|
|
|
|
//get app's version
|
|
// extracted from Info.plist
|
|
NSString* getAppVersion()
|
|
{
|
|
//read and return 'CFBundleVersion' from bundle
|
|
return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
|
|
}
|
|
|
|
//get path to (main) app
|
|
// login item is in app bundle, so parse up to get main app
|
|
NSString* getMainAppPath()
|
|
{
|
|
//path components
|
|
NSArray *pathComponents = nil;
|
|
|
|
//path to config (main) app
|
|
NSString* mainApp = nil;
|
|
|
|
//get path components
|
|
// then build full path to main app
|
|
pathComponents = [[[NSBundle mainBundle] bundlePath] pathComponents];
|
|
if(pathComponents.count > 4)
|
|
{
|
|
//init path to full (main) app
|
|
mainApp = [NSString pathWithComponents:[pathComponents subarrayWithRange:NSMakeRange(0, pathComponents.count - 4)]];
|
|
}
|
|
|
|
//when (still) nil
|
|
// use default path
|
|
if(nil == mainApp)
|
|
{
|
|
//default
|
|
mainApp = [@"/Applications" stringByAppendingPathComponent:APP_NAME];
|
|
}
|
|
|
|
return mainApp;
|
|
}
|
|
|
|
//get name of logged in user
|
|
NSString* getConsoleUser()
|
|
{
|
|
//copy/return user
|
|
return CFBridgingRelease(SCDynamicStoreCopyConsoleUser(NULL, NULL, NULL));
|
|
}
|
|
|
|
//verify that an app bundle is
|
|
// a) signed
|
|
// b) signed with signing auth
|
|
OSStatus verifyApp(NSString* path, NSString* signingAuth)
|
|
{
|
|
//status
|
|
OSStatus status = !noErr;
|
|
|
|
//signing req string
|
|
NSString *requirementString = nil;
|
|
|
|
//code
|
|
SecStaticCodeRef staticCode = NULL;
|
|
|
|
//signing reqs
|
|
SecRequirementRef requirementRef = NULL;
|
|
|
|
//init requirement string
|
|
requirementString = [NSString stringWithFormat:@"anchor trusted and certificate leaf [subject.CN] = \"%@\"", signingAuth];
|
|
|
|
//create static code
|
|
status = SecStaticCodeCreateWithPath((__bridge CFURLRef)([NSURL fileURLWithPath:path]), kSecCSDefaultFlags, &staticCode);
|
|
if(noErr != status)
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"SecStaticCodeCreateWithPath failed w/ %d", status]);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//create req string
|
|
status = SecRequirementCreateWithString((__bridge CFStringRef _Nonnull)(requirementString), kSecCSDefaultFlags, &requirementRef);
|
|
if( (noErr != status) ||
|
|
(requirementRef == NULL) )
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"SecRequirementCreateWithString failed w/ %d", status]);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//check if file is signed w/ apple dev id by checking if it conforms to req string
|
|
status = SecStaticCodeCheckValidity(staticCode, kSecCSDefaultFlags, requirementRef);
|
|
if(noErr != status)
|
|
{
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"SecStaticCodeCheckValidity failed w/ %d", status]);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//happy
|
|
status = noErr;
|
|
|
|
bail:
|
|
|
|
//free req reference
|
|
if(NULL != requirementRef)
|
|
{
|
|
//free
|
|
CFRelease(requirementRef);
|
|
requirementRef = NULL;
|
|
}
|
|
|
|
//free static code
|
|
if(NULL != staticCode)
|
|
{
|
|
//free
|
|
CFRelease(staticCode);
|
|
staticCode = NULL;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
//get state of lid
|
|
int getLidState()
|
|
{
|
|
//state
|
|
int state = stateUnavailable;
|
|
|
|
//registry entry for power management
|
|
io_registry_entry_t powerManagmentRE = MACH_PORT_NULL;
|
|
|
|
//reference to 'kAppleClamshellStateKey' property
|
|
CFBooleanRef clamshellState = NULL;
|
|
|
|
//get registry entry for power management root domain
|
|
powerManagmentRE = IORegistryEntryFromPath(kIOMasterPortDefault, kIOPowerPlane ":/IOPowerConnection/IOPMrootDomain");
|
|
if(MACH_PORT_NULL == powerManagmentRE)
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, @"failed to look up the registry entry for 'IOPMrootDomain'");
|
|
|
|
//error
|
|
goto bail;
|
|
}
|
|
|
|
//get reference to state of 'kAppleClamshellStateKey'
|
|
clamshellState = (CFBooleanRef)IORegistryEntryCreateCFProperty(powerManagmentRE, CFSTR(kAppleClamshellStateKey), kCFAllocatorDefault, 0);
|
|
if(NULL == clamshellState)
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, @"failed to get property for 'kAppleClamshellStateKey'");
|
|
|
|
//error
|
|
goto bail;
|
|
}
|
|
|
|
//get state
|
|
state = (LidState)CFBooleanGetValue(clamshellState);
|
|
|
|
bail:
|
|
|
|
//release
|
|
if(NULL != clamshellState)
|
|
{
|
|
//release
|
|
CFRelease(clamshellState);
|
|
|
|
//unset
|
|
clamshellState = NULL;
|
|
}
|
|
|
|
//release
|
|
if(MACH_PORT_NULL != powerManagmentRE)
|
|
{
|
|
//release
|
|
IOObjectRelease(powerManagmentRE);
|
|
|
|
//unset
|
|
powerManagmentRE = MACH_PORT_NULL;
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
//set dir's|file's group/owner
|
|
BOOL setFileOwner(NSString* path, NSNumber* groupID, NSNumber* ownerID, BOOL recursive)
|
|
{
|
|
//ret var
|
|
BOOL bRet = NO;
|
|
|
|
//owner dictionary
|
|
NSDictionary* fileOwner = nil;
|
|
|
|
//sub paths
|
|
NSArray *subPaths = nil;
|
|
|
|
//full path
|
|
// for recursive
|
|
NSString* fullPath = nil;
|
|
|
|
//init permissions dictionary
|
|
fileOwner = @{NSFileGroupOwnerAccountID:groupID, NSFileOwnerAccountID:ownerID};
|
|
|
|
//set group/owner
|
|
if(YES != [[NSFileManager defaultManager] setAttributes:fileOwner ofItemAtPath:path error:NULL])
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set ownership for %@ (%@)", path, fileOwner]);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"set ownership for %@ (%@)", path, fileOwner]);
|
|
|
|
//do it recursively
|
|
if(YES == recursive)
|
|
{
|
|
//sanity check
|
|
// make sure root starts with '/'
|
|
if(YES != [path hasSuffix:@"/"])
|
|
{
|
|
//add '/'
|
|
path = [NSString stringWithFormat:@"%@/", path];
|
|
}
|
|
|
|
//get all subpaths
|
|
subPaths = [[NSFileManager defaultManager] subpathsAtPath:path];
|
|
for(NSString *subPath in subPaths)
|
|
{
|
|
//init full path
|
|
fullPath = [path stringByAppendingString:subPath];
|
|
|
|
//set group/owner
|
|
if(YES != [[NSFileManager defaultManager] setAttributes:fileOwner ofItemAtPath:fullPath error:NULL])
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set ownership for %@ (%@)", fullPath, fileOwner]);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
}
|
|
}
|
|
|
|
//no errors
|
|
bRet = YES;
|
|
|
|
bail:
|
|
|
|
return bRet;
|
|
}
|
|
|
|
//given a pid
|
|
// get process's path
|
|
NSString* getProcessPath(pid_t pid)
|
|
{
|
|
//task path
|
|
NSString* processPath = nil;
|
|
|
|
//buffer for process path
|
|
char pathBuffer[PROC_PIDPATHINFO_MAXSIZE] = {0};
|
|
|
|
//status
|
|
int status = -1;
|
|
|
|
//'management info base' array
|
|
int mib[3] = {0};
|
|
|
|
//system's size for max args
|
|
int systemMaxArgs = 0;
|
|
|
|
//process's args
|
|
char* taskArgs = NULL;
|
|
|
|
//# of args
|
|
int numberOfArgs = 0;
|
|
|
|
//size of buffers, etc
|
|
size_t size = 0;
|
|
|
|
//reset buffer
|
|
bzero(pathBuffer, PROC_PIDPATHINFO_MAXSIZE);
|
|
|
|
//first attempt to get path via 'proc_pidpath()'
|
|
status = proc_pidpath(pid, pathBuffer, sizeof(pathBuffer));
|
|
if(0 != status)
|
|
{
|
|
//init task's name
|
|
processPath = [NSString stringWithUTF8String:pathBuffer];
|
|
}
|
|
//otherwise
|
|
// try via task's args ('KERN_PROCARGS2')
|
|
else
|
|
{
|
|
//init mib
|
|
// want system's size for max args
|
|
mib[0] = CTL_KERN;
|
|
mib[1] = KERN_ARGMAX;
|
|
|
|
//set size
|
|
size = sizeof(systemMaxArgs);
|
|
|
|
//get system's size for max args
|
|
if(-1 == sysctl(mib, 2, &systemMaxArgs, &size, NULL, 0))
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//alloc space for args
|
|
taskArgs = (char*)malloc((size_t)systemMaxArgs);
|
|
if(NULL == taskArgs)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//init mib
|
|
// want process args
|
|
mib[0] = CTL_KERN;
|
|
mib[1] = KERN_PROCARGS2;
|
|
mib[2] = pid;
|
|
|
|
//set size
|
|
size = (size_t)systemMaxArgs;
|
|
|
|
//get process's args
|
|
if(-1 == sysctl(mib, 3, taskArgs, &size, NULL, 0))
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//sanity check
|
|
// ensure buffer is somewhat sane
|
|
if(size <= sizeof(int))
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//extract number of args
|
|
// at start of buffer
|
|
memcpy(&numberOfArgs, taskArgs, sizeof(numberOfArgs));
|
|
|
|
//extract task's name
|
|
// follows # of args (int) and is NULL-terminated
|
|
processPath = [NSString stringWithUTF8String:taskArgs + sizeof(int)];
|
|
}
|
|
|
|
bail:
|
|
|
|
//free process args
|
|
if(NULL != taskArgs)
|
|
{
|
|
//free
|
|
free(taskArgs);
|
|
|
|
//reset
|
|
taskArgs = NULL;
|
|
}
|
|
|
|
return processPath;
|
|
}
|
|
|
|
//exec a process with args
|
|
// if 'shouldWait' is set, wait and return stdout/in and termination status
|
|
NSMutableDictionary* execTask(NSString* binaryPath, NSArray* arguments, BOOL shouldWait)
|
|
{
|
|
//task
|
|
NSTask* task = nil;
|
|
|
|
//output pipe for stdout
|
|
NSPipe* stdOutPipe = nil;
|
|
|
|
//output pipe for stderr
|
|
NSPipe* stdErrPipe = nil;
|
|
|
|
//read handle for stdout
|
|
NSFileHandle* stdOutReadHandle = nil;
|
|
|
|
//read handle for stderr
|
|
NSFileHandle* stdErrReadHandle = nil;
|
|
|
|
//results dictionary
|
|
NSMutableDictionary* results = nil;
|
|
|
|
//output for stdout
|
|
NSMutableData *stdOutData = nil;
|
|
|
|
//output for stderr
|
|
NSMutableData *stdErrData = nil;
|
|
|
|
//init dictionary for results
|
|
results = [NSMutableDictionary dictionary];
|
|
|
|
//init task
|
|
task = [NSTask new];
|
|
|
|
//only setup pipes if wait flag is set
|
|
if(YES == shouldWait)
|
|
{
|
|
//init stdout pipe
|
|
stdOutPipe = [NSPipe pipe];
|
|
|
|
//init stderr pipe
|
|
stdErrPipe = [NSPipe pipe];
|
|
|
|
//init stdout read handle
|
|
stdOutReadHandle = [stdOutPipe fileHandleForReading];
|
|
|
|
//init stderr read handle
|
|
stdErrReadHandle = [stdErrPipe fileHandleForReading];
|
|
|
|
//init stdout output buffer
|
|
stdOutData = [NSMutableData data];
|
|
|
|
//init stderr output buffer
|
|
stdErrData = [NSMutableData data];
|
|
|
|
//set task's stdout
|
|
task.standardOutput = stdOutPipe;
|
|
|
|
//set task's stderr
|
|
task.standardError = stdErrPipe;
|
|
}
|
|
|
|
//set task's path
|
|
task.launchPath = binaryPath;
|
|
|
|
//set task's args
|
|
if(nil != arguments)
|
|
{
|
|
//set
|
|
task.arguments = arguments;
|
|
}
|
|
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"execing task, %@ (arguments: %@)", task.launchPath, task.arguments]);
|
|
|
|
//wrap task launch
|
|
@try
|
|
{
|
|
//launch
|
|
[task launch];
|
|
}
|
|
@catch(NSException *exception)
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to launch task (%@)", exception]);
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//no need to wait
|
|
// can just bail w/ no output
|
|
if(YES != shouldWait)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//read in stdout/stderr
|
|
while(YES == [task isRunning])
|
|
{
|
|
//accumulate stdout
|
|
[stdOutData appendData:[stdOutReadHandle readDataToEndOfFile]];
|
|
|
|
//accumulate stderr
|
|
[stdErrData appendData:[stdErrReadHandle readDataToEndOfFile]];
|
|
}
|
|
|
|
//grab any leftover stdout
|
|
[stdOutData appendData:[stdOutReadHandle readDataToEndOfFile]];
|
|
|
|
//grab any leftover stderr
|
|
[stdErrData appendData:[stdErrReadHandle readDataToEndOfFile]];
|
|
|
|
//add stdout
|
|
if(0 != stdOutData.length)
|
|
{
|
|
//add
|
|
results[STDOUT] = stdOutData;
|
|
}
|
|
|
|
//add stderr
|
|
if(0 != stdErrData.length)
|
|
{
|
|
//add
|
|
results[STDERR] = stdErrData;
|
|
}
|
|
|
|
//add exit code
|
|
results[EXIT_CODE] = [NSNumber numberWithInteger:task.terminationStatus];
|
|
|
|
bail:
|
|
|
|
return results;
|
|
}
|
|
|
|
//given a process path and user
|
|
// return array of all matching pids
|
|
NSMutableArray* getProcessIDs(NSString* processPath, int userID)
|
|
{
|
|
//status
|
|
int status = -1;
|
|
|
|
//process IDs
|
|
NSMutableArray* processIDs = nil;
|
|
|
|
//# of procs
|
|
int numberOfProcesses = 0;
|
|
|
|
//array of pids
|
|
pid_t* pids = NULL;
|
|
|
|
//process info struct
|
|
struct kinfo_proc procInfo;
|
|
|
|
//size of struct
|
|
size_t procInfoSize = 0;
|
|
|
|
//mib
|
|
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, -1};
|
|
|
|
//init size
|
|
procInfoSize = sizeof(struct kinfo_proc);
|
|
|
|
//zero out
|
|
memset(&procInfo, 0, procInfoSize);
|
|
|
|
//get # of procs
|
|
numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
|
|
|
|
//alloc buffer for pids
|
|
pids = calloc((size_t)numberOfProcesses, sizeof(pid_t));
|
|
|
|
//alloc
|
|
processIDs = [NSMutableArray array];
|
|
|
|
//get list of pids
|
|
status = proc_listpids(PROC_ALL_PIDS, 0, pids, numberOfProcesses * (int)sizeof(pid_t));
|
|
if(status < 0)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//iterate over all pids
|
|
// get name for each process
|
|
for(int i = 0; i < numberOfProcesses; i++)
|
|
{
|
|
//skip blank pids
|
|
if(0 == pids[i])
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//skip if path doesn't match
|
|
if(YES != [processPath isEqualToString:getProcessPath(pids[i])])
|
|
{
|
|
//next
|
|
continue;
|
|
}
|
|
|
|
//need to also match on user?
|
|
// caller can pass in -1 to skip this check
|
|
if(-1 != userID)
|
|
{
|
|
//init mib
|
|
mib[0x3] = pids[i];
|
|
|
|
//make syscall to get proc info for user
|
|
if( (0 != sysctl(mib, 0x4, &procInfo, &procInfoSize, NULL, 0)) ||
|
|
(0 == procInfoSize) )
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//skip if user id doesn't match
|
|
if(userID != (int)procInfo.kp_eproc.e_ucred.cr_uid)
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//got match
|
|
// add to list
|
|
[processIDs addObject:[NSNumber numberWithInt:pids[i]]];
|
|
}
|
|
|
|
bail:
|
|
|
|
//free buffer
|
|
if(NULL != pids)
|
|
{
|
|
//free
|
|
free(pids);
|
|
|
|
//reset
|
|
pids = NULL;
|
|
}
|
|
|
|
return processIDs;
|
|
}
|
|
|
|
//wait until a window is non nil
|
|
// then make it modal
|
|
void makeModal(NSWindowController* windowController)
|
|
{
|
|
//wait up to 1 second window to be non-nil
|
|
// then make modal
|
|
for(int i=0; i<20; i++)
|
|
{
|
|
//can make it modal once we have a window
|
|
if(nil != windowController.window)
|
|
{
|
|
//make modal on main thread
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
|
|
//modal
|
|
[[NSApplication sharedApplication] runModalForWindow:windowController.window];
|
|
|
|
});
|
|
|
|
//all done
|
|
break;
|
|
}
|
|
|
|
//nap
|
|
[NSThread sleepForTimeInterval:0.05f];
|
|
|
|
}//until 1 second
|
|
|
|
return;
|
|
}
|
|
|
|
//check if an instance of an app is already running
|
|
BOOL isAppRunning(NSString* bundleID)
|
|
{
|
|
//flag
|
|
BOOL alreadyRunning = NO;
|
|
|
|
//aleady an instance?
|
|
// make that instance active and then bail
|
|
for(NSRunningApplication* runningApp in [NSRunningApplication runningApplicationsWithBundleIdentifier:bundleID])
|
|
{
|
|
//another instance that's not this?
|
|
if(YES != [runningApp isEqual:[NSRunningApplication currentApplication]])
|
|
{
|
|
//set flag
|
|
alreadyRunning = YES;
|
|
|
|
//make (already) running instance first
|
|
[runningApp activateWithOptions:NSApplicationActivateAllWindows|NSApplicationActivateIgnoringOtherApps];
|
|
|
|
//done looking
|
|
break;
|
|
}
|
|
}
|
|
|
|
return alreadyRunning;
|
|
}
|
|
|
|
//for login item enable/disable
|
|
// we use the launch services APIs, since replacements don't always work :(
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
|
|
//toggle login item
|
|
// either add (install) or remove (uninstall)
|
|
BOOL toggleLoginItem(NSURL* loginItem, int toggleFlag)
|
|
{
|
|
//flag
|
|
BOOL wasToggled = NO;
|
|
|
|
//login item ref
|
|
LSSharedFileListRef loginItemsRef = NULL;
|
|
|
|
//login items
|
|
CFArrayRef loginItems = NULL;
|
|
|
|
//current login item
|
|
CFURLRef currentLoginItem = NULL;
|
|
|
|
//get reference to login items
|
|
loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
|
|
|
|
//add (install)
|
|
if(ACTION_INSTALL_FLAG == toggleFlag)
|
|
{
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"adding login item %@", loginItem]);
|
|
|
|
//add
|
|
LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, (__bridge CFURLRef)(loginItem), NULL, NULL);
|
|
|
|
//release item ref
|
|
if(NULL != itemRef)
|
|
{
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"added %@/%@", loginItem, itemRef]);
|
|
|
|
//release
|
|
CFRelease(itemRef);
|
|
|
|
//reset
|
|
itemRef = NULL;
|
|
}
|
|
//failed
|
|
else
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, @"failed to add login item");
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//happy
|
|
wasToggled = YES;
|
|
}
|
|
//remove (uninstall)
|
|
else
|
|
{
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"removing login item %@", loginItem]);
|
|
|
|
//grab existing login items
|
|
loginItems = LSSharedFileListCopySnapshot(loginItemsRef, nil);
|
|
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu login items", (unsigned long)((__bridge NSArray *)loginItems).count]);
|
|
|
|
//iterate over all login items
|
|
// look for self, then remove it
|
|
for(id item in (__bridge NSArray *)loginItems)
|
|
{
|
|
//get current login item
|
|
currentLoginItem = LSSharedFileListItemCopyResolvedURL((__bridge LSSharedFileListItemRef)item, 0, NULL);
|
|
if(NULL == currentLoginItem)
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//current login item match self?
|
|
if(YES == [(__bridge NSURL *)currentLoginItem isEqual:loginItem])
|
|
{
|
|
//remove
|
|
if(noErr != LSSharedFileListItemRemove(loginItemsRef, (__bridge LSSharedFileListItemRef)item))
|
|
{
|
|
//err msg
|
|
logMsg(LOG_ERR, @"failed to remove login item");
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"removed login item: %@", loginItem]);
|
|
|
|
//happy
|
|
wasToggled = YES;
|
|
|
|
//all done
|
|
goto bail;
|
|
}
|
|
|
|
//release
|
|
CFRelease(currentLoginItem);
|
|
|
|
//reset
|
|
currentLoginItem = NULL;
|
|
|
|
}//all login items
|
|
|
|
}//remove/uninstall
|
|
|
|
bail:
|
|
|
|
//release login items
|
|
if(NULL != loginItems)
|
|
{
|
|
//release
|
|
CFRelease(loginItems);
|
|
|
|
//reset
|
|
loginItems = NULL;
|
|
}
|
|
|
|
//release login ref
|
|
if(NULL != loginItemsRef)
|
|
{
|
|
//release
|
|
CFRelease(loginItemsRef);
|
|
|
|
//reset
|
|
loginItemsRef = NULL;
|
|
}
|
|
|
|
//release url
|
|
if(NULL != currentLoginItem)
|
|
{
|
|
//release
|
|
CFRelease(currentLoginItem);
|
|
|
|
//reset
|
|
currentLoginItem = NULL;
|
|
}
|
|
|
|
return wasToggled;
|
|
}
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
//touchID capable?
|
|
BOOL hasTouchID()
|
|
{
|
|
//flag
|
|
BOOL hasTouchID = NO;
|
|
|
|
//local auth context
|
|
LAContext* localauthContext = nil;
|
|
|
|
//error
|
|
NSError* error = nil;
|
|
|
|
//alloc/init
|
|
localauthContext = [[LAContext alloc] init];
|
|
|
|
//eval w/ try/catch as it can throw!
|
|
// note though we ignore "Code=-4 Touch Bar is not available in closed clamshell mode"
|
|
@try
|
|
{
|
|
//eval
|
|
if( (YES != [localauthContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) &&
|
|
(error.code != -4) )
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
//exception likely means 'unknown policy'
|
|
// so we can safely ignore/bail and 'hasTouchID' won't be set
|
|
@catch(NSException *exception)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//macOS only supports touchID
|
|
// TODO: update this if macOS ever has faceID! (see check below)
|
|
hasTouchID = YES;
|
|
|
|
/*
|
|
//set flag
|
|
if(@available(macOS 10.13.2, *))
|
|
{
|
|
hasTouchID = (LABiometryTypeTouchID == localauthContext.biometryType);
|
|
}
|
|
*/
|
|
|
|
bail:
|
|
|
|
return hasTouchID;
|
|
}
|
|
|
|
//get current console user
|
|
NSString* currentConsoleUser(SCDynamicStoreRef store)
|
|
{
|
|
//user
|
|
NSString* consoleUser = nil;
|
|
|
|
//grab console user
|
|
consoleUser = CFBridgingRelease(SCDynamicStoreCopyConsoleUser(store, NULL, NULL));
|
|
if(nil == consoleUser)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//ignore 'loginwindow'
|
|
if(YES == [consoleUser isEqualToString:@"loginwindow"])
|
|
{
|
|
//unset
|
|
consoleUser = nil;
|
|
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
bail:
|
|
|
|
return consoleUser;
|
|
}
|
|
|
|
//macOS Mojave+ gotta request camera access
|
|
// note, we check first, to see if request was already made
|
|
void requestCameraAccess()
|
|
{
|
|
//on macOS 10.14+
|
|
// trigger request for camera access
|
|
if(@available(macOS 10.14, *))
|
|
{
|
|
//auth status
|
|
AVAuthorizationStatus authStatus = AVAuthorizationStatusNotDetermined;
|
|
|
|
//get current status
|
|
authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
|
|
|
//already requested?
|
|
if(authStatus != AVAuthorizationStatusNotDetermined)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//request
|
|
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted)
|
|
{
|
|
//dbg msg
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"user response for camera access: %d", granted]);
|
|
}];
|
|
}
|
|
|
|
bail:
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//check if (true) dark mode
|
|
BOOL isDarkMode()
|
|
{
|
|
//flag
|
|
BOOL darkMode = NO;
|
|
|
|
//not mojave?
|
|
// bail, since not true dark mode
|
|
if(YES != [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){10, 14, 0}])
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//not dark mode?
|
|
if(YES != [[[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"] isEqualToString:@"Dark"])
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//ok, mojave dark mode it is!
|
|
darkMode = YES;
|
|
|
|
bail:
|
|
|
|
return darkMode;
|
|
}
|