OverSight/Shared/Utilities.m

701 lines
16 KiB
Mathematica
Raw Normal View History

2016-09-12 01:21:14 +01:00
//
// Utilities.m
// WhatsYourSign
//
// Created by Patrick Wardle on 7/7/16.
// Copyright (c) 2016 Objective-See. All rights reserved.
//
#import "Consts.h"
#import "Logging.h"
2016-09-12 01:21:14 +01:00
#import "Utilities.h"
#import <signal.h>
#import <unistd.h>
#import <libproc.h>
#import <sys/stat.h>
2016-09-12 01:21:14 +01:00
#import <sys/sysctl.h>
#import <Security/Security.h>
#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonDigest.h>
#import <SystemConfiguration/SystemConfiguration.h>
//get OS version
NSDictionary* getOSVersion()
{
//os version info
NSMutableDictionary* osVersionInfo = nil;
//major v
SInt32 majorVersion = 0;
//minor v
SInt32 minorVersion = 0;
//alloc dictionary
osVersionInfo = [NSMutableDictionary dictionary];
//get major version
if(STATUS_SUCCESS != Gestalt(gestaltSystemVersionMajor, &majorVersion))
{
//reset
osVersionInfo = nil;
//bail
goto bail;
}
//get minor version
if(STATUS_SUCCESS != Gestalt(gestaltSystemVersionMinor, &minorVersion))
{
//reset
osVersionInfo = nil;
//bail
goto bail;
}
//set major version
osVersionInfo[@"majorVersion"] = [NSNumber numberWithInteger:majorVersion];
//set minor version
osVersionInfo[@"minorVersion"] = [NSNumber numberWithInteger:minorVersion];
//bail
bail:
return osVersionInfo;
}
2016-09-12 01:21:14 +01:00
//get app's version
// ->extracted from Info.plist
NSString* getAppVersion()
{
//read and return 'CFBundleVersion' from bundle
return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
}
//given a path to binary
// parse it back up to find app's bundle
NSBundle* findAppBundle(NSString* binaryPath)
{
//app's bundle
NSBundle* appBundle = nil;
//app's path
NSString* appPath = nil;
//first just try full path
appPath = binaryPath;
//try to find the app's bundle/info dictionary
do
{
//try to load app's bundle
appBundle = [NSBundle bundleWithPath:appPath];
//check for match
// ->binary path's match
if( (nil != appBundle) &&
(YES == [appBundle.executablePath isEqualToString:binaryPath]))
{
//all done
break;
}
//always unset bundle var since it's being returned
// ->and at this point, its not a match
appBundle = nil;
//remove last part
// ->will try this next
appPath = [appPath stringByDeletingLastPathComponent];
//scan until we get to root
// ->of course, loop will exit if app info dictionary is found/loaded
} while( (nil != appPath) &&
(YES != [appPath isEqualToString:@"/"]) &&
(YES != [appPath isEqualToString:@""]) );
return appBundle;
}
//set dir's|file's group/owner
BOOL setFileOwner(NSString* path, NSNumber* groupID, NSNumber* ownerID, BOOL recursive)
{
//ret var
BOOL bSetOwner = 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
bSetOwner = YES;
//bail
bail:
return bSetOwner;
}
//set permissions for file
BOOL setFilePermissions(NSString* file, int permissions, BOOL recursive)
{
//ret var
BOOL bSetPermissions = NO;
//file permissions
NSDictionary* filePermissions = nil;
//root directory
NSURL* root = nil;
//directory enumerator
NSDirectoryEnumerator* enumerator = nil;
//error
NSError* error = nil;
//init dictionary
filePermissions = @{NSFilePosixPermissions: [NSNumber numberWithInt:permissions]};
//apply file permissions recursively
if(YES == recursive)
{
//init root
root = [NSURL fileURLWithPath:file];
//init enumerator
enumerator = [[NSFileManager defaultManager] enumeratorAtURL:root includingPropertiesForKeys:[NSArray arrayWithObject:NSURLIsDirectoryKey] options:0
errorHandler:^(NSURL *url, NSError *error) { return YES; }];
//set file permissions on each
for(NSURL* currentFile in enumerator)
{
//set permissions
if(YES != [[NSFileManager defaultManager] setAttributes:filePermissions ofItemAtPath:currentFile.path error:&error])
{
//err msg
logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set permissions for %@ (%@), %@", currentFile.path, filePermissions, error]);
//bail
goto bail;
}
}
}
//always set permissions on passed in file (or top-level directory)
// ->note: recursive enumerator skips root directory, so execute this always
if(YES != [[NSFileManager defaultManager] setAttributes:filePermissions ofItemAtPath:file error:NULL])
{
//err msg
logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set permissions for %@ (%@)", file, filePermissions]);
//bail
goto bail;
}
//happy
bSetPermissions = YES;
//bail
bail:
return bSetPermissions;
}
2016-09-12 01:21:14 +01:00
//exec a process and grab it's output
NSData* execTask(NSString* binaryPath, NSArray* arguments)
{
//task
NSTask *task = nil;
//output pipe
NSPipe *outPipe = nil;
//read handle
NSFileHandle* readHandle = nil;
//output
NSMutableData *output = nil;
//init task
task = [NSTask new];
//init output pipe
outPipe = [NSPipe pipe];
//init read handle
readHandle = [outPipe fileHandleForReading];
//init output buffer
output = [NSMutableData data];
//set task's path
[task setLaunchPath:binaryPath];
//set task's args
[task setArguments:arguments];
//set task's output
[task setStandardOutput:outPipe];
//wrap task launch
@try
{
//launch
[task launch];
}
@catch(NSException* exception)
2016-09-12 01:21:14 +01:00
{
//bail
goto bail;
}
//read in output
while(YES == [task isRunning])
{
//accumulate output
[output appendData:[readHandle readDataToEndOfFile]];
}
//grab any left over data
[output appendData:[readHandle readDataToEndOfFile]];
//bail
bail:
return output;
}
//get OS's major or minor version
SInt32 getVersion(OSType selector)
{
//version
// ->major or minor
SInt32 version = -1;
//get version info
if(noErr != Gestalt(selector, &version))
{
//reset version
version = -1;
//err
goto bail;
}
//bail
bail:
return version;
}
//get process's path
NSString* getProcessPath(pid_t pid)
{
//task path
NSString* taskPath = 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
taskPath = [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 = malloc(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
taskPath = [NSString stringWithUTF8String:taskArgs + sizeof(int)];
}
//bail
bail:
//free process args
if(NULL != taskArgs)
{
//free
free(taskArgs);
//reset
taskArgs = NULL;
}
return taskPath;
}
//given a pid
// ->get the name of the process
NSString* getProcessName(pid_t pid)
{
//task path
NSString* processName = nil;
//process path
NSString* processPath = nil;
//app's bundle
NSBundle* appBundle = nil;
//get process path
processPath = getProcessPath(pid);
if( (nil == processPath) ||
(0 == processPath.length) )
{
//default to 'unknown'
processName = @"<unknown>";
//bail
goto bail;
}
//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];
}
//bail
bail:
return processName;
}
//given a process name
// ->get the (first) instance of that process
pid_t getProcessID(NSString* processName)
{
//status
int status = -1;
//process id
pid_t processID = -1;
//# of procs
int numberOfProcesses = 0;
//array of pids
pid_t* pids = NULL;
//get # of procs
numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
//alloc buffer for pids
pids = calloc(numberOfProcesses, sizeof(pid_t));
//get list of pids
status = proc_listpids(PROC_ALL_PIDS, 0, pids, numberOfProcesses * sizeof(pid_t));
if(status < 0)
{
//err
//syslog(LOG_ERR, "OBJECTIVE-SEE ERROR: proc_listpids() failed with %d", status);
//bail
goto bail;
}
//iterate over all pids
// ->get name for each
for(int i = 0; i < numberOfProcesses; ++i)
{
//skip blank pids
if(0 == pids[i])
{
//skip
continue;
}
//skip if name doesn't match
if(YES != [processName isEqualToString:getProcessName(pids[i])])
{
//next
continue;
}
//got match
processID = pids[i];
//exit loop
break;
}
//bail
bail:
//free buffer
if(NULL != pids)
{
//free
free(pids);
//reset
pids = NULL;
}
return processID;
}
//determine if there is a new version
// -1, YES or NO
NSInteger isNewVersion(NSMutableString* versionString)
{
//flag
NSInteger newVersionExists = -1;
//installed version
NSString* installedVersion = nil;
//latest version
NSString* latestVersion = nil;
//get installed version
installedVersion = getAppVersion();
//get latest version
// ->will query internet (bb's website)
latestVersion = getLatestVersion();
if(nil == latestVersion)
{
//set error msg
[versionString setString:@"failed to get latest version"];
//bail
goto bail;
}
//save version
[versionString setString:latestVersion];
//set version flag
// ->YES/NO
newVersionExists = (NSOrderedAscending == [installedVersion compare:latestVersion options:NSNumericSearch]);
//bail
bail:
return newVersionExists;
}
//query interwebz to get latest version
NSString* getLatestVersion()
{
//version data
NSData* versionData = nil;
//version dictionary
NSDictionary* versionDictionary = nil;
//latest version
NSString* latestVersion = nil;
//get version from remote URL
versionData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:PRODUCT_VERSION_URL]];
//sanity check
if(nil == versionData)
{
//bail
goto bail;
}
//convert JSON to dictionary
versionDictionary = [NSJSONSerialization JSONObjectWithData:versionData options:0 error:nil];
//sanity check
if(nil == versionDictionary)
{
//bail
goto bail;
}
//extract latest version
latestVersion = versionDictionary[@"latestVersion"];
//bail
bail:
return latestVersion;
}
//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;
}