#include <stdio.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <notify.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCPrivate.h> // for SCLog()
#include <SystemConfiguration/SCDPlugin.h>
#include <SystemConfiguration/SCValidation.h>
typedef struct {
boolean_t active;
boolean_t needsKick;
CFDictionaryRef dict;
CFRunLoopRef rl;
CFRunLoopSourceRef rls;
SCDynamicStoreRef store;
CFMutableArrayRef changedKeys;
} kickee, *kickeeRef;
static CFURLRef myBundleURL = NULL;
static Boolean _verbose = FALSE;
static void booter(kickeeRef target);
static void booterExit(pid_t pid, int status, struct rusage *rusage, void *context);
static void
cleanupKicker(kickeeRef target)
{
CFStringRef name = CFDictionaryGetValue(target->dict, CFSTR("name"));
SCLog(TRUE, LOG_NOTICE,
CFSTR(" target=%@: disabled"),
name);
CFRunLoopRemoveSource(target->rl, target->rls, kCFRunLoopDefaultMode);
CFRelease(target->rls);
CFRelease(target->store);
if (target->dict) CFRelease(target->dict);
if (target->changedKeys) CFRelease(target->changedKeys);
CFAllocatorDeallocate(NULL, target);
}
static void
booter(kickeeRef target)
{
char **argv = NULL;
char *cmd = NULL;
CFStringRef execCommand = CFDictionaryGetValue(target->dict, CFSTR("execCommand"));
int i;
CFArrayRef keys = NULL;
CFStringRef name = CFDictionaryGetValue(target->dict, CFSTR("name"));
int nKeys = 0;
Boolean ok = FALSE;
CFStringRef postName = CFDictionaryGetValue(target->dict, CFSTR("postName"));
if (target->active) {
target->needsKick = TRUE;
SCLog(_verbose, LOG_DEBUG, CFSTR("Kicker callback, target=%@ request queued"), name);
return;
}
SCLog(_verbose, LOG_DEBUG, CFSTR("Kicker callback, target=%@"), name);
if (!isA_CFString(postName) && !isA_CFString(execCommand)) {
goto error;
}
if (isA_CFString(postName)) {
uint32_t status;
cmd = _SC_cfstring_to_cstring(postName, NULL, 0, kCFStringEncodingASCII);
if (!cmd) {
SCLog(TRUE, LOG_DEBUG, CFSTR(" could not convert post name to C string"));
goto error;
}
SCLog(TRUE, LOG_NOTICE, CFSTR("posting notification %s"), cmd);
status = notify_post(cmd);
if (status != NOTIFY_STATUS_OK) {
SCLog(TRUE, LOG_DEBUG, CFSTR(" notify_post() failed: error=%ld"), status);
goto error;
}
CFAllocatorDeallocate(NULL, cmd);
cmd = NULL;
}
keys = target->changedKeys;
target->changedKeys = NULL;
if (isA_CFString(execCommand)) {
CFRange bpr;
CFNumberRef execGID = CFDictionaryGetValue(target->dict, CFSTR("execGID"));
CFNumberRef execUID = CFDictionaryGetValue(target->dict, CFSTR("execUID"));
CFBooleanRef passKeys = CFDictionaryGetValue(target->dict, CFSTR("changedKeysAsArguments"));
gid_t reqGID = 0;
uid_t reqUID = 0;
CFMutableStringRef str;
str = CFStringCreateMutableCopy(NULL, 0, execCommand);
bpr = CFStringFind(str, CFSTR("$BUNDLE"), 0);
if (bpr.location != kCFNotFound) {
CFStringRef bundlePath;
bundlePath = CFURLCopyFileSystemPath(myBundleURL, kCFURLPOSIXPathStyle);
CFStringReplace(str, bpr, bundlePath);
CFRelease(bundlePath);
}
cmd = _SC_cfstring_to_cstring(str, NULL, 0, kCFStringEncodingASCII);
CFRelease(str);
if (!cmd) {
SCLog(TRUE, LOG_DEBUG, CFSTR(" could not convert command to C string"));
goto error;
}
if (isA_CFNumber(execUID)) {
CFNumberGetValue(execUID, kCFNumberIntType, &reqUID);
}
if (isA_CFNumber(execGID)) {
CFNumberGetValue(execGID, kCFNumberIntType, &reqGID);
}
nKeys = CFArrayGetCount(keys);
argv = CFAllocatorAllocate(NULL, (nKeys + 2) * sizeof(char *), 0);
for (i = 0; i < (nKeys + 2); i++) {
argv[i] = NULL;
}
if ((argv[0] = rindex(cmd, '/')) != NULL) {
argv[0]++;
} else {
argv[0] = cmd;
}
if (isA_CFBoolean(passKeys) && CFBooleanGetValue(passKeys)) {
for (i = 0; i < nKeys; i++) {
CFStringRef key = CFArrayGetValueAtIndex(keys, i);
argv[i+1] = _SC_cfstring_to_cstring(key, NULL, 0, kCFStringEncodingASCII);
if (!argv[i+1]) {
SCLog(TRUE, LOG_DEBUG, CFSTR(" could not convert argument to C string"));
goto error;
}
}
}
SCLog(TRUE, LOG_NOTICE, CFSTR("executing %s"), cmd);
SCLog(_verbose, LOG_DEBUG, CFSTR(" current uid = %d, requested = %d"), geteuid(), reqUID);
target->active = TRUE;
(void)_SCDPluginExecCommand(booterExit,
target,
reqUID,
reqGID,
cmd,
argv);
}
ok = TRUE;
error :
if (keys) CFRelease(keys);
if (cmd) CFAllocatorDeallocate(NULL, cmd);
if (argv) {
for (i = 0; i < nKeys; i++) {
if (argv[i+1]) {
CFAllocatorDeallocate(NULL, argv[i+1]);
}
}
CFAllocatorDeallocate(NULL, argv);
}
if (!ok) {
cleanupKicker(target);
}
return;
}
static void
booterExit(pid_t pid, int status, struct rusage *rusage, void *context)
{
CFStringRef name;
Boolean ok = TRUE;
kickeeRef target = (kickeeRef)context;
name = CFDictionaryGetValue(target->dict, CFSTR("name"));
target->active = FALSE;
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;
}
if (!ok) {
if (CFDictionaryContainsKey(target->dict, CFSTR("postName"))) {
CFDictionaryRef oldDict = target->dict;
CFMutableDictionaryRef newDict = CFDictionaryCreateMutableCopy(NULL, 0, oldDict);
CFDictionaryRemoveValue(newDict, CFSTR("execCommand"));
CFDictionaryRemoveValue(newDict, CFSTR("execGID"));
CFDictionaryRemoveValue(newDict, CFSTR("execUID"));
CFDictionaryRemoveValue(newDict, CFSTR("changedKeysAsArguments"));
target->dict = newDict;
CFRelease(oldDict);
} else {
cleanupKicker(target);
target = NULL;
}
}
if (target != NULL && target->needsKick) {
target->needsKick = FALSE;
booter(target);
}
return;
}
static void
kicker(SCDynamicStoreRef store, CFArrayRef changedKeys, void *arg)
{
CFIndex i;
CFIndex n = CFArrayGetCount(changedKeys);
kickeeRef target = (kickeeRef)arg;
if (!target->changedKeys) {
target->changedKeys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
}
for (i = 0; i < n; i++) {
CFStringRef key = CFArrayGetValueAtIndex(changedKeys, i);
if (!CFArrayContainsValue(target->changedKeys,
CFRangeMake(0, CFArrayGetCount(target->changedKeys)),
key)) {
CFArrayAppendValue(target->changedKeys, key);
}
}
booter(target);
return;
}
static 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 };
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;
}
static CFArrayRef
getTargets(CFBundleRef bundle)
{
Boolean ok;
CFArrayRef targets;
CFURLRef url;
CFStringRef xmlError;
CFDataRef xmlTargets = NULL;
url = CFBundleCopyResourceURL(bundle, CFSTR("Kicker"), CFSTR("xml"), NULL);
if (url == NULL) {
return NULL;
}
ok = CFURLCreateDataAndPropertiesFromResource(NULL, url, &xmlTargets, NULL, NULL, NULL);
CFRelease(url);
if (!ok || (xmlTargets == NULL)) {
return NULL;
}
targets = CFPropertyListCreateFromXMLData(NULL,
xmlTargets,
kCFPropertyListImmutable,
&xmlError);
CFRelease(xmlTargets);
if (targets == NULL) {
if (xmlError != NULL) {
SCLog(TRUE, LOG_DEBUG, CFSTR("getTargets(): %@"), xmlError);
CFRelease(xmlError);
}
return NULL;
}
if (!isA_CFArray(targets)) {
CFRelease(targets);
targets = NULL;
}
return targets;
}
__private_extern__
void
load_Kicker(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 == NULL) {
return;
}
targets = getTargets(bundle);
if (targets == NULL) {
CFRelease(myBundleURL);
return;
}
CFArrayApplyFunction(targets,
CFRangeMake(0, CFArrayGetCount(targets)),
startKicker,
NULL);
CFRelease(targets);
return;
}
#ifdef MAIN
int
main(int argc, char * const argv[])
{
_sc_log = FALSE;
_sc_verbose = (argc > 1) ? TRUE : FALSE;
load_Kicker(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
CFRunLoopRun();
exit(0);
return 0;
}
#endif