#include <stdio.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCPrivate.h> // for SCLog()
#include <SystemConfiguration/SCValidation.h>
typedef struct {
pthread_mutex_t lock;
boolean_t active;
boolean_t needsKick;
pthread_t helper;
CFDictionaryRef dict;
CFRunLoopRef rl;
CFRunLoopSourceRef rls;
SCDynamicStoreRef store;
CFMutableArrayRef changedKeys;
} kickee, *kickeeRef;
static CFURLRef myBundleURL = NULL;
static Boolean _verbose = FALSE;
int
execCommandWithUID(const char *command, const char *argv[], uid_t reqUID)
{
uid_t curUID = geteuid();
pid_t pid;
int status;
int i;
SCLog(TRUE, LOG_NOTICE, CFSTR("executing %s"), command);
SCLog(_verbose, LOG_DEBUG, CFSTR(" current uid = %d, requested = %d"), curUID, reqUID);
pid = fork();
switch (pid) {
case -1 :
status = W_EXITCODE(errno, 0);
SCLog(TRUE, LOG_DEBUG, CFSTR("vfork() failed: %s"), strerror(errno));
return status;
case 0 :
if ((curUID != reqUID) && (curUID == 0)) {
(void) setuid(reqUID);
}
for (i = getdtablesize()-1; i>=0; i--) close(i);
open("/dev/null", O_RDWR, 0);
dup(0);
dup(0);
execv(command, argv);
status = W_EXITCODE(errno, 0);
SCLog(TRUE, LOG_DEBUG, CFSTR("execl() failed: %s"), strerror(errno));
_exit (WEXITSTATUS(status));
}
pid = wait4(pid, &status, 0, 0);
return (pid == -1) ? -1 : status;
}
void
cleanupTarget(void *ptr)
{
kickeeRef target = (kickeeRef)ptr;
SCLog(_verbose, LOG_DEBUG, CFSTR("SCDynamicStore callback thread cancelled, cleaning up \"target\""));
pthread_mutex_destroy(&target->lock);
if (target->dict) CFRelease(target->dict);
if (target->changedKeys) CFRelease(target->changedKeys);
CFAllocatorDeallocate(NULL, target);
return;
}
void
cleanupCFObject(void *ptr)
{
SCLog(_verbose, LOG_DEBUG, CFSTR("SCDynamicStore callback thread cancelled, cleaning up \"theCommand\" string"));
if (ptr != NULL)
CFRelease(ptr);
return;
}
typedef struct {
char *cmd;
char **argv;
} execInfo, *execInfoRef;
void
cleanupArgInfo(void *ptr)
{
execInfoRef info = (execInfoRef)ptr;
SCLog(_verbose, LOG_DEBUG, CFSTR("SCDynamicStore callback thread cancelled, cleaning up \"cmd\" buffer"));
if (info == NULL) {
return;
}
if (info->cmd) {
CFAllocatorDeallocate(NULL, info->cmd);
}
if (info->argv) {
int argc = 1;
while (info->argv[argc] != NULL) {
CFAllocatorDeallocate(NULL, info->argv[argc]);
argc++;
}
CFAllocatorDeallocate(NULL, info->argv);
}
return;
}
void *
booter(void *arg)
{
kickeeRef target = (kickeeRef)arg;
CFStringRef name = CFDictionaryGetValue(target->dict, CFSTR("name"));
CFStringRef execCommand = CFDictionaryGetValue(target->dict, CFSTR("execCommand"));
CFNumberRef execUID = CFDictionaryGetValue(target->dict, CFSTR("execUID"));
CFBooleanRef passKeys = CFDictionaryGetValue(target->dict, CFSTR("changedKeysAsArguments"));
SCLog(_verbose, LOG_DEBUG, CFSTR("Kicker callback, target=%@"), name);
pthread_cleanup_push(cleanupTarget, (void *)target);
if (isA_CFString(execCommand)) {
CFMutableStringRef theCommand;
CFRange bpr;
Boolean ok = TRUE;
uid_t reqUID = 0;
int status;
theCommand = CFStringCreateMutableCopy(NULL, 0, execCommand);
pthread_cleanup_push(cleanupCFObject, (void *)theCommand);
bpr = CFStringFind(theCommand, CFSTR("$BUNDLE"), 0);
if (bpr.location != kCFNotFound) {
CFStringRef bundlePath;
bundlePath = CFURLCopyFileSystemPath(myBundleURL, kCFURLPOSIXPathStyle);
CFStringReplace(theCommand, bpr, bundlePath);
CFRelease(bundlePath);
}
if (isA_CFNumber(execUID)) {
CFNumberGetValue(execUID, kCFNumberIntType, &reqUID);
}
pthread_mutex_lock(&target->lock);
while (ok && target->needsKick) {
int i;
execInfo info;
int len;
int nKeys = CFArrayGetCount(target->changedKeys);
info.cmd = NULL;
info.argv = CFAllocatorAllocate(NULL,
(nKeys + 2) * sizeof(char *),
0);
for (i=0; i<(nKeys + 2); i++) {
info.argv[i] = NULL;
}
pthread_cleanup_push(cleanupArgInfo, &info);
len = CFStringGetLength(theCommand) + 1;
info.cmd = CFAllocatorAllocate(NULL, len, 0);
ok = CFStringGetCString(theCommand,
info.cmd,
len,
kCFStringEncodingMacRoman);
if (!ok) {
SCLog(TRUE, LOG_DEBUG, CFSTR(" could not convert command to C string"));
goto error;
}
if ((info.argv[0] = rindex(info.cmd, '/')) != NULL) {
info.argv[0]++;
} else {
info.argv[0] = info.cmd;
}
if (isA_CFBoolean(passKeys) && CFBooleanGetValue(passKeys)) {
for (i=0; i<nKeys; i++) {
CFStringRef key = CFArrayGetValueAtIndex(target->changedKeys, i);
len = CFStringGetLength(key) + 1;
info.argv[i+1] = CFAllocatorAllocate(NULL, len, 0);
ok = CFStringGetCString(key,
info.argv[i+1],
len,
kCFStringEncodingMacRoman);
if (!ok) {
SCLog(TRUE, LOG_DEBUG,
CFSTR(" could not convert argument to C string"));
goto error;
}
}
}
CFRelease(target->changedKeys);
target->changedKeys = NULL;
target->needsKick = FALSE;
pthread_mutex_unlock(&target->lock);
status = execCommandWithUID(info.cmd, info.argv, reqUID);
if (WIFEXITED(status)) {
SCLog(TRUE, LOG_DEBUG,
CFSTR(" target=%@: exit status = %d"),
name,
WEXITSTATUS(status));
if (WEXITSTATUS(status) != 0) {
ok = FALSE;
}
} else if (WIFSIGNALED(status)) {
SCLog(TRUE, LOG_DEBUG,
CFSTR(" target=%@: terminated w/signal = %d"),
name,
WTERMSIG(status));
ok = FALSE;
} else {
SCLog(TRUE, LOG_DEBUG,
CFSTR(" target=%@: exit status = %d"),
name,
status);
ok = FALSE;
}
error :
pthread_cleanup_pop(1);
pthread_mutex_lock(&target->lock);
}
target->active = FALSE;
pthread_mutex_unlock(&target->lock);
pthread_cleanup_pop(1);
if (!ok) {
SCLog(TRUE, LOG_NOTICE,
CFSTR("Kicker: retries disabled (command failed), target=%@"),
name);
CFRunLoopRemoveSource(target->rl, target->rls, kCFRunLoopDefaultMode);
CFRelease(target->store);
}
}
pthread_cleanup_pop(0);
pthread_exit (NULL);
return NULL;
}
void
kicker(SCDynamicStoreRef store, CFArrayRef changedKeys, void *arg)
{
CFIndex i;
kickeeRef target = (kickeeRef)arg;
pthread_attr_t tattr;
pthread_mutex_lock(&target->lock);
if (!target->changedKeys) {
target->changedKeys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
}
for (i=0; i<CFArrayGetCount(changedKeys); i++) {
CFStringRef key = CFArrayGetValueAtIndex(changedKeys, i);
if (!CFArrayContainsValue(target->changedKeys,
CFRangeMake(0, CFArrayGetCount(target->changedKeys)),
key)) {
CFArrayAppendValue(target->changedKeys, key);
}
}
if (target->active) {
CFStringRef name = CFDictionaryGetValue(target->dict, CFSTR("name"));
target->needsKick = TRUE;
pthread_mutex_unlock(&target->lock);
SCLog(_verbose, LOG_DEBUG, CFSTR("Kicker callback, target=%@ request queued"), name);
return;
}
target->active = TRUE;
target->needsKick = TRUE;
pthread_mutex_unlock(&target->lock);
pthread_attr_init(&tattr);
pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM);
pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
pthread_attr_setstacksize(&tattr, 96 * 1024); pthread_create(&target->helper, &tattr, booter, (void *)target);
pthread_attr_destroy(&tattr);
return;
}
void
startKicker(const void *value, void *context)
{
CFMutableStringRef name;
CFArrayRef keys;
CFArrayRef patterns;
kickeeRef target = CFAllocatorAllocate(NULL, sizeof(kickee), 0);
SCDynamicStoreContext targetContext = { 0, (void *)target, NULL, NULL, NULL };
pthread_mutex_init(&target->lock, NULL);
target->active = FALSE;
target->needsKick = FALSE;
target->dict = CFRetain((CFDictionaryRef)value);
target->store = NULL;
target->rl = NULL;
target->rls = NULL;
target->changedKeys = NULL;
name = CFStringCreateMutableCopy(NULL,
0,
CFDictionaryGetValue(target->dict, CFSTR("name")));
SCLog(TRUE, LOG_DEBUG, CFSTR("Starting kicker for %@"), name);
CFStringAppend(name, CFSTR(" \"Kicker\""));
target->store = SCDynamicStoreCreate(NULL, name, kicker, &targetContext);
CFRelease(name);
if (!target->store) {
SCLog(TRUE,
LOG_NOTICE,
CFSTR("SCDynamicStoreCreate() failed: %s"),
SCErrorString(SCError()));
goto error;
}
keys = isA_CFArray(CFDictionaryGetValue(target->dict, CFSTR("keys")));
patterns = isA_CFArray(CFDictionaryGetValue(target->dict, CFSTR("regexKeys")));
if (!SCDynamicStoreSetNotificationKeys(target->store, keys, patterns)) {
SCLog(TRUE,
LOG_NOTICE,
CFSTR("SCDynamicStoreSetNotifications() failed: %s"),
SCErrorString(SCError()));
goto error;
}
target->rl = CFRunLoopGetCurrent();
target->rls = SCDynamicStoreCreateRunLoopSource(NULL, target->store, 0);
if (!target->rls) {
SCLog(TRUE,
LOG_NOTICE,
CFSTR("SCDynamicStoreCreateRunLoopSource() failed: %s"),
SCErrorString(SCError()));
goto error;
}
CFRunLoopAddSource(target->rl, target->rls, kCFRunLoopDefaultMode);
return;
error :
CFRelease(target->dict);
if (target->store) CFRelease(target->store);
CFAllocatorDeallocate(NULL, target);
return;
}
CFArrayRef
getTargets(CFBundleRef bundle)
{
int fd;
Boolean ok;
struct stat statBuf;
CFArrayRef targets;
char targetPath[MAXPATHLEN];
CFURLRef url;
CFStringRef xmlError;
CFMutableDataRef xmlTargets;
url = CFBundleCopyResourceURL(bundle, CFSTR("Kicker"), CFSTR("xml"), NULL);
if (!url) {
return NULL;
}
ok = CFURLGetFileSystemRepresentation(url,
TRUE,
(UInt8 *)&targetPath,
sizeof(targetPath));
CFRelease(url);
if (!ok) {
return NULL;
}
if ((fd = open(targetPath, O_RDONLY, 0)) == -1) {
SCLog(TRUE, LOG_NOTICE,
CFSTR("%@ load(): %s not found"),
CFBundleGetIdentifier(bundle),
targetPath);
return NULL;
}
if (fstat(fd, &statBuf) < 0) {
(void) close(fd);
return NULL;
}
if ((statBuf.st_mode & S_IFMT) != S_IFREG) {
(void) close(fd);
return NULL;
}
xmlTargets = CFDataCreateMutable(NULL, statBuf.st_size);
CFDataSetLength(xmlTargets, statBuf.st_size);
if (read(fd, (void *)CFDataGetBytePtr(xmlTargets), statBuf.st_size) != statBuf.st_size) {
CFRelease(xmlTargets);
(void) close(fd);
return NULL;
}
(void) close(fd);
targets = CFPropertyListCreateFromXMLData(NULL,
xmlTargets,
kCFPropertyListImmutable,
&xmlError);
CFRelease(xmlTargets);
if (!targets) {
if (xmlError) {
SCLog(TRUE, LOG_DEBUG,
CFSTR("CFPropertyListCreateFromXMLData() start: %@"),
xmlError);
CFRelease(xmlError);
}
return NULL;
}
if (!isA_CFArray(targets)) {
CFRelease(targets);
targets = NULL;
}
return targets;
}
void
load(CFBundleRef bundle, Boolean bundleVerbose)
{
CFArrayRef targets;
if (bundleVerbose) {
_verbose = TRUE;
}
SCLog(_verbose, LOG_DEBUG, CFSTR("load() called"));
SCLog(_verbose, LOG_DEBUG, CFSTR(" bundle ID = %@"), CFBundleGetIdentifier(bundle));
myBundleURL = CFBundleCopyBundleURL(bundle);
if (!myBundleURL) {
return;
}
targets = getTargets(bundle);
if (!targets) {
CFRelease(myBundleURL);
return;
}
CFArrayApplyFunction(targets,
CFRangeMake(0, CFArrayGetCount(targets)),
startKicker,
NULL);
CFRelease(targets);
return;
}
#ifdef MAIN
int
main(int argc, char **argv)
{
_sc_log = FALSE;
_sc_verbose = (argc > 1) ? TRUE : FALSE;
load(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
CFRunLoopRun();
exit(0);
return 0;
}
#endif