/* * Copyright (c) 2000-2004 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Modification History * * April 16, 2002 Allan Nathanson * - updated to use _SCDPluginExecCommand() * * June 23, 2001 Allan Nathanson * - updated to public SystemConfiguration.framework APIs * * June 4, 2001 Allan Nathanson * - add changed keys as the arguments to the kicker script * * June 30, 2000 Allan Nathanson * - initial revision */ #include #include #include #include #include #include #include #include #include // for SCLog() #include #include /* * Information maintained for each to-be-kicked registration. */ typedef struct { boolean_t active; boolean_t needsKick; /* dictionary associated with this target */ CFDictionaryRef dict; /* SCDynamicStore session information for this target */ CFRunLoopRef rl; CFRunLoopSourceRef rls; SCDynamicStoreRef store; /* changed keys */ 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) { /* we need another kick! */ 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 no notifications to post nor commands to execute */ } if (isA_CFString(postName)) { uint32_t status; /* * post a notification */ 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); /* clean up */ cmd = NULL; } /* * get the arguments for the kickee */ 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; /* * build the kickee command */ 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; } /* * get the UID/GID for the kickee */ 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; } /* create command name argument */ if ((argv[0] = rindex(cmd, '/')) != NULL) { argv[0]++; } else { argv[0] = cmd; } /* create changed key arguments */ 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); /* this kicker is now "running" */ target->active = TRUE; (void)_SCDPluginExecCommand(booterExit, target, reqUID, reqGID, cmd, argv); // CFAllocatorDeallocate(NULL, cmd); /* clean up */ // cmd = NULL; } 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) { /* * If the target action can't be performed this time then * there's not much point in trying again. As such, I close * the session and the kickee target released. */ 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); /* * if this target specifies both a BSD notification and * a script to be executed then we want to continue to * post the BSD notifications (and not execute the * script). As such, remove the script reference from * the dictionary. */ CFDictionaryRemoveValue(newDict, CFSTR("execCommand")); CFDictionaryRemoveValue(newDict, CFSTR("execGID")); CFDictionaryRemoveValue(newDict, CFSTR("execUID")); CFDictionaryRemoveValue(newDict, CFSTR("changedKeysAsArguments")); target->dict = newDict; CFRelease(oldDict); } else { /* * If the target action can't be performed this time then * there's not much point in trying again. As such, I close * the session and the kickee target released. */ 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; /* * Start a new kicker. If a kicker was already active then flag * the need for a second kick after the active one completes. */ /* create (or add to) the full list of keys that have changed */ 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); } } /* * let 'er rip. */ booter(target); return; } /* * startKicker() * * The first argument is a dictionary representing the keys * which need to be monitored for a given "target" and what * action should be taken if a change in one of those keys * is detected. */ 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; /* The array of dictionaries representing targets with a "kick me" sign posted on their backs. */ CFURLRef url; CFStringRef xmlError; CFDataRef xmlTargets = NULL; /* locate the Kicker targets */ url = CFBundleCopyResourceURL(bundle, CFSTR("Kicker"), CFSTR("xml"), NULL); if (url == NULL) { return NULL; } /* read the resource data */ ok = CFURLCreateDataAndPropertiesFromResource(NULL, url, &xmlTargets, NULL, NULL, NULL); CFRelease(url); if (!ok || (xmlTargets == NULL)) { return NULL; } /* convert the XML data into a property list */ 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; /* The array of dictionaries representing targets * with a "kick me" sign posted on their backs.*/ if (bundleVerbose) { _verbose = TRUE; } SCLog(_verbose, LOG_DEBUG, CFSTR("load() called")); SCLog(_verbose, LOG_DEBUG, CFSTR(" bundle ID = %@"), CFBundleGetIdentifier(bundle)); /* get the bundle's URL */ myBundleURL = CFBundleCopyBundleURL(bundle); if (myBundleURL == NULL) { return; } /* get the targets */ targets = getTargets(bundle); if (targets == NULL) { /* if nothing to do */ CFRelease(myBundleURL); return; } /* start a kicker for each target */ 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(); /* not reached */ exit(0); return 0; } #endif