OverSight/Shared/Utilities.m

1203 lines
28 KiB
Objective-C

//
// Utilities.m
// OverSight
//
// Created by Patrick Wardle on 7/7/16.
// Copyright (c) 2016 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 <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;
}
//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
#ifdef DEBUG
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"set ownership for %@ (%@)", path, fileOwner]);
#endif
//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;
}
//exec a process and grab it's output
NSData* execTask(NSString* binaryPath, NSArray* arguments, BOOL shouldWait)
{
//task
NSTask *task = nil;
//output pipe
NSPipe *outPipe = nil;
//output
NSData *output = nil;
//dispatch group
dispatch_group_t dispatchGroup = 0;
//init task
task = [[NSTask alloc] init];
//init pipe
outPipe = [NSPipe pipe];
//create dispatch group
dispatchGroup = dispatch_group_create();
//set task's path
task.launchPath = binaryPath;
//set task's args
if(nil != arguments)
{
//add
task.arguments = arguments;
}
//set task's output to pipe
// ->but only if we're waiting for exit
if(YES == shouldWait)
{
//redirect
task.standardOutput = outPipe;
}
//dbg msg
#ifdef DEBUG
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"@exec'ing %@ (args: %@)", binaryPath, arguments]);
#endif
//enter dispatch
dispatch_group_enter(dispatchGroup);
//set task's termination to leave dispatch group
task.terminationHandler = ^(NSTask *task){
//leave
dispatch_group_leave(dispatchGroup);
};
//wrap task launch
@try
{
//launch
[task launch];
}
@catch(NSException* exception)
{
//err msg
logMsg(LOG_ERR, [NSString stringWithFormat:@"task failed with %@", exception]);
//bail
goto bail;
}
//when waiting
// ->grab data
if(YES == shouldWait)
{
//dbg msg
#ifdef DEBUG
logMsg(LOG_DEBUG, @"invoking 'readDataToEndOfFile' to get all data");
#endif
//read until file is closed
output = [outPipe.fileHandleForReading readDataToEndOfFile];
//dbg msg
#ifdef DEBUG
logMsg(LOG_DEBUG, @"now waiting for task to exit");
#endif
//wait till exit
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
//dbg msg
#ifdef DEBUG
logMsg(LOG_DEBUG, @"task exited");
#endif
}//wait
//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, uid_t userID)
{
//status
int status = -1;
//process id
pid_t processID = -1;
//# of procs
int numberOfProcesses = 0;
//array of pids
pid_t* pids = NULL;
//process info struct
struct kinfo_proc procInfo = {0};
//size of struct
size_t procInfoSize = sizeof(procInfo);
//mib
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, -1};
//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)
{
//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;
}
//init mib
mib[0x3] = pids[i];
//make syscall to get proc info
if( (0 != sysctl(mib, 0x4, &procInfo, &procInfoSize, NULL, 0)) ||
(0 == procInfoSize) )
{
//skip
continue;
}
//skip if user id doesn't match
if(userID != procInfo.kp_eproc.e_ucred.cr_uid)
{
//skip
continue;
}
//got match
processID = pids[i];
//exit loop
break;
}
//bail
bail:
//free buffer
if(NULL != pids)
{
//free
free(pids);
//reset
pids = NULL;
}
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)
{
//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 (obj-see 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()
{
//product version(s) data
NSData* productsVersionData = nil;
//version dictionary
NSDictionary* productsVersionDictionary = nil;
//latest version
NSString* latestVersion = nil;
//get version from remote URL
productsVersionData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:PRODUCT_VERSIONS_URL]];
if(nil == productsVersionData)
{
//bail
goto bail;
}
//convert JSON to dictionary
// ->wrap as may throw exception
@try
{
//convert
productsVersionDictionary = [NSJSONSerialization JSONObjectWithData:productsVersionData options:0 error:nil];
if(nil == productsVersionDictionary)
{
//bail
goto bail;
}
}
@catch(NSException* exception)
{
//bail
goto bail;
}
//extract latest version
latestVersion = [[productsVersionDictionary objectForKey:@"OverSight"] objectForKey:@"version"];
//dbg msg
#ifdef DEBUG
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"latest version: %@", latestVersion]);
#endif
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;
}
//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
#ifdef DEBUG
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"adding login item %@", loginItem]);
#endif
//add
LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, (__bridge CFURLRef)(loginItem), NULL, NULL);
//release item ref
if(NULL != itemRef)
{
//dbg msg
#ifdef DEBUG
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"added %@/%@", loginItem, itemRef]);
#endif
//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
#ifdef DEBUG
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"removing login item %@", loginItem]);
#endif
//grab existing login items
loginItems = LSSharedFileListCopySnapshot(loginItemsRef, nil);
//iterate over all login items
// ->look for self, then remove it/them
for (id item in (__bridge NSArray *)loginItems)
{
//get current login item
if( (noErr != LSSharedFileListItemResolve((__bridge LSSharedFileListItemRef)item, 0, (CFURLRef*)&currentLoginItem, NULL)) ||
(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 loginItem: %@", loginItem]);
//happy
wasToggled = YES;
}
//release
if(NULL != currentLoginItem)
{
//release
CFRelease(currentLoginItem);
//reset
currentLoginItem = NULL;
}
}//all login items
}//remove/uninstall
//bail
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;
}
return wasToggled;
}
//get logged in user
// name, uid, and gid
NSMutableDictionary* loggedinUser()
{
//user info
NSMutableDictionary* userInfo = nil;
//store
SCDynamicStoreRef store = nil;
//user
NSString* user = nil;
//uid
uid_t uid = 0;
//gid
gid_t gid = 0;
//allco dictionary
userInfo = [NSMutableDictionary dictionary];
//create store
store = SCDynamicStoreCreate(NULL, CFSTR("GetConsoleUser"), NULL, NULL);
if(NULL == store)
{
//bail
goto bail;
}
//get user and uid/gid
user = CFBridgingRelease(SCDynamicStoreCopyConsoleUser(store, &uid, &gid));
//add user
userInfo[@"user"] = user;
//add uid
userInfo[@"uid"] = [NSNumber numberWithUnsignedInt:uid];
//add uid
userInfo[@"gid"] = [NSNumber numberWithUnsignedInt:gid];
//bail
bail:
//release store
if(NULL != store)
{
//release
CFRelease(store);
}
return userInfo;
}
//find a process by name
pid_t findProcess(NSString* processName)
{
//pid
pid_t processID = 0;
//status
int status = -1;
//# of procs
int numberOfProcesses = 0;
//array of pids
pid_t* pids = NULL;
//process path
NSString* processPath = nil;
//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)
{
//bail
goto bail;
}
//iterate over all pids
// ->get name for each via helper function
for(int i = 0; i < numberOfProcesses; ++i)
{
//skip blank pids
if(0 == pids[i])
{
//skip
continue;
}
//get name
processPath = getProcessPath(pids[i]);
if( (nil == processPath) ||
(0 == processPath.length) )
{
//skip
continue;
}
//match?
if(YES == [processPath isEqualToString:processName])
{
//save
processID = pids[i];
//pau
break;
}
}//all procs
//bail
bail:
//free buffer
if(NULL != pids)
{
//free
free(pids);
}
return processID;
}
//convert a textview to a clickable hyperlink
void makeTextViewHyperlink(NSTextField* textField, NSURL* url)
{
//hyperlink
NSMutableAttributedString *hyperlinkString = nil;
//range
NSRange range = {0};
//init hyper link
hyperlinkString = [[NSMutableAttributedString alloc] initWithString:textField.stringValue];
//init range
range = NSMakeRange(0, [hyperlinkString length]);
//start editing
[hyperlinkString beginEditing];
//add url
[hyperlinkString addAttribute:NSLinkAttributeName value:url range:range];
//make it blue
[hyperlinkString addAttribute:NSForegroundColorAttributeName value:[NSColor blueColor] range:NSMakeRange(0, [hyperlinkString length])];
//underline
[hyperlinkString addAttribute:
NSUnderlineStyleAttributeName value:[NSNumber numberWithInt:NSSingleUnderlineStyle] range:NSMakeRange(0, [hyperlinkString length])];
//done editing
[hyperlinkString endEditing];
//set text
[textField setAttributedStringValue:hyperlinkString];
return;
}
//get frontmost (active) app
pid_t frontmostApplication()
{
//get/ret
return NSWorkspace.sharedWorkspace.frontmostApplication.processIdentifier;
}
//check if process is alive
BOOL isProcessAlive(pid_t processID)
{
//ret var
BOOL bIsAlive = NO;
//signal status
int signalStatus = -1;
//send kill with 0 to determine if alive
// -> see: http://stackoverflow.com/questions/9152979/check-if-process-exists-given-its-pid
signalStatus = kill(processID, 0);
//is alive?
if( (0 == signalStatus) ||
( (0 != signalStatus) && (errno != ESRCH) ) )
{
//dbg msg
#ifdef DEBUG
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"agent (%d) is ALIVE", processID]);
#endif
//alive!
bIsAlive = YES;
}
return bIsAlive;
}