]> git.saurik.com Git - apple/configd.git/blob - Plugins/Kicker/kicker.c
configd-289.tar.gz
[apple/configd.git] / Plugins / Kicker / kicker.c
1 /*
2 * Copyright (c) 2000-2005 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*
25 * Modification History
26 *
27 * April 16, 2002 Allan Nathanson <ajn@apple.com>
28 * - updated to use _SCDPluginExecCommand()
29 *
30 * June 23, 2001 Allan Nathanson <ajn@apple.com>
31 * - updated to public SystemConfiguration.framework APIs
32 *
33 * June 4, 2001 Allan Nathanson <ajn@apple.com>
34 * - add changed keys as the arguments to the kicker script
35 *
36 * June 30, 2000 Allan Nathanson <ajn@apple.com>
37 * - initial revision
38 */
39
40 #include <stdio.h>
41 #include <fcntl.h>
42 #include <pthread.h>
43 #include <unistd.h>
44 #include <sys/types.h>
45 #include <sys/wait.h>
46 #include <notify.h>
47
48 #include <SystemConfiguration/SystemConfiguration.h>
49 #include <SystemConfiguration/SCPrivate.h> // for SCLog()
50 #include <SystemConfiguration/SCDPlugin.h>
51 #include <SystemConfiguration/SCValidation.h>
52
53 /*
54 * Information maintained for each to-be-kicked registration.
55 */
56 typedef struct {
57 boolean_t active;
58 boolean_t needsKick;
59
60 /* dictionary associated with this target */
61 CFDictionaryRef dict;
62
63 /* SCDynamicStore session information for this target */
64 CFRunLoopRef rl;
65 CFRunLoopSourceRef rls;
66 SCDynamicStoreRef store;
67
68 /* changed keys */
69 CFMutableArrayRef changedKeys;
70 } kickee, *kickeeRef;
71
72 static CFURLRef myBundleURL = NULL;
73 static Boolean _verbose = FALSE;
74
75 static void booter(kickeeRef target);
76 static void booterExit(pid_t pid, int status, struct rusage *rusage, void *context);
77
78
79 static void
80 cleanupKicker(kickeeRef target)
81 {
82 CFStringRef name = CFDictionaryGetValue(target->dict, CFSTR("name"));
83
84 SCLog(TRUE, LOG_NOTICE,
85 CFSTR(" target=%@: disabled"),
86 name);
87 CFRunLoopRemoveSource(target->rl, target->rls, kCFRunLoopDefaultMode);
88 CFRelease(target->rls);
89 CFRelease(target->store);
90 if (target->dict) CFRelease(target->dict);
91 if (target->changedKeys) CFRelease(target->changedKeys);
92 CFAllocatorDeallocate(NULL, target);
93 }
94
95
96 static void
97 booter(kickeeRef target)
98 {
99 char **argv = NULL;
100 char *cmd = NULL;
101 CFStringRef execCommand = CFDictionaryGetValue(target->dict, CFSTR("execCommand"));
102 int i;
103 CFArrayRef keys = NULL;
104 CFStringRef name = CFDictionaryGetValue(target->dict, CFSTR("name"));
105 int nKeys = 0;
106 Boolean ok = FALSE;
107 CFStringRef postName = CFDictionaryGetValue(target->dict, CFSTR("postName"));
108
109 if (target->active) {
110 /* we need another kick! */
111 target->needsKick = TRUE;
112
113 SCLog(_verbose, LOG_DEBUG, CFSTR("Kicker callback, target=%@ request queued"), name);
114 return;
115 }
116
117 SCLog(_verbose, LOG_DEBUG, CFSTR("Kicker callback, target=%@"), name);
118
119 if (!isA_CFString(postName) && !isA_CFString(execCommand)) {
120 goto error; /* if no notifications to post nor commands to execute */
121 }
122
123 if (isA_CFString(postName)) {
124 uint32_t status;
125
126 /*
127 * post a notification
128 */
129 cmd = _SC_cfstring_to_cstring(postName, NULL, 0, kCFStringEncodingASCII);
130 if (!cmd) {
131 SCLog(TRUE, LOG_DEBUG, CFSTR(" could not convert post name to C string"));
132 goto error;
133 }
134
135 SCLog(TRUE, LOG_NOTICE, CFSTR("posting notification %s"), cmd);
136 status = notify_post(cmd);
137 if (status != NOTIFY_STATUS_OK) {
138 SCLog(TRUE, LOG_DEBUG, CFSTR(" notify_post() failed: error=%ld"), status);
139 goto error;
140 }
141
142 CFAllocatorDeallocate(NULL, cmd); /* clean up */
143 cmd = NULL;
144 }
145
146 /*
147 * get the arguments for the kickee
148 */
149 keys = target->changedKeys;
150 target->changedKeys = NULL;
151
152 if (isA_CFString(execCommand)) {
153 CFRange bpr;
154 CFNumberRef execGID = CFDictionaryGetValue(target->dict, CFSTR("execGID"));
155 CFNumberRef execUID = CFDictionaryGetValue(target->dict, CFSTR("execUID"));
156 CFBooleanRef passKeys = CFDictionaryGetValue(target->dict, CFSTR("changedKeysAsArguments"));
157 gid_t reqGID = 0;
158 uid_t reqUID = 0;
159 CFMutableStringRef str;
160
161 /*
162 * build the kickee command
163 */
164 str = CFStringCreateMutableCopy(NULL, 0, execCommand);
165 bpr = CFStringFind(str, CFSTR("$BUNDLE"), 0);
166 if (bpr.location != kCFNotFound) {
167 CFStringRef bundlePath;
168
169 bundlePath = CFURLCopyFileSystemPath(myBundleURL, kCFURLPOSIXPathStyle);
170 CFStringReplace(str, bpr, bundlePath);
171 CFRelease(bundlePath);
172 }
173
174 cmd = _SC_cfstring_to_cstring(str, NULL, 0, kCFStringEncodingASCII);
175 CFRelease(str);
176 if (!cmd) {
177 SCLog(TRUE, LOG_DEBUG, CFSTR(" could not convert command to C string"));
178 goto error;
179 }
180
181 /*
182 * get the UID/GID for the kickee
183 */
184 if (isA_CFNumber(execUID)) {
185 CFNumberGetValue(execUID, kCFNumberIntType, &reqUID);
186 }
187
188 if (isA_CFNumber(execGID)) {
189 CFNumberGetValue(execGID, kCFNumberIntType, &reqGID);
190 }
191
192 nKeys = CFArrayGetCount(keys);
193 argv = CFAllocatorAllocate(NULL, (nKeys + 2) * sizeof(char *), 0);
194 for (i = 0; i < (nKeys + 2); i++) {
195 argv[i] = NULL;
196 }
197
198 /* create command name argument */
199 if ((argv[0] = rindex(cmd, '/')) != NULL) {
200 argv[0]++;
201 } else {
202 argv[0] = cmd;
203 }
204
205 /* create changed key arguments */
206 if (isA_CFBoolean(passKeys) && CFBooleanGetValue(passKeys)) {
207 for (i = 0; i < nKeys; i++) {
208 CFStringRef key = CFArrayGetValueAtIndex(keys, i);
209
210 argv[i+1] = _SC_cfstring_to_cstring(key, NULL, 0, kCFStringEncodingASCII);
211 if (!argv[i+1]) {
212 SCLog(TRUE, LOG_DEBUG, CFSTR(" could not convert argument to C string"));
213 goto error;
214 }
215 }
216 }
217
218 SCLog(TRUE, LOG_NOTICE, CFSTR("executing %s"), cmd);
219 SCLog(_verbose, LOG_DEBUG, CFSTR(" current uid = %d, requested = %d"), geteuid(), reqUID);
220
221 /* this kicker is now "running" */
222 target->active = TRUE;
223
224 (void)_SCDPluginExecCommand(booterExit,
225 target,
226 reqUID,
227 reqGID,
228 cmd,
229 argv);
230
231 // CFAllocatorDeallocate(NULL, cmd); /* clean up */
232 // cmd = NULL;
233 }
234 ok = TRUE;
235
236 error :
237
238 if (keys) CFRelease(keys);
239 if (cmd) CFAllocatorDeallocate(NULL, cmd);
240 if (argv) {
241 for (i = 0; i < nKeys; i++) {
242 if (argv[i+1]) {
243 CFAllocatorDeallocate(NULL, argv[i+1]);
244 }
245 }
246 CFAllocatorDeallocate(NULL, argv);
247 }
248
249 if (!ok) {
250 /*
251 * If the target action can't be performed this time then
252 * there's not much point in trying again. As such, I close
253 * the session and the kickee target released.
254 */
255 cleanupKicker(target);
256 }
257
258 return;
259 }
260
261
262 static void
263 booterExit(pid_t pid, int status, struct rusage *rusage, void *context)
264 {
265 CFStringRef name;
266 Boolean ok = TRUE;
267 kickeeRef target = (kickeeRef)context;
268
269 name = CFDictionaryGetValue(target->dict, CFSTR("name"));
270 target->active = FALSE;
271 if (WIFEXITED(status)) {
272 SCLog(TRUE, LOG_DEBUG,
273 CFSTR(" target=%@: exit status = %d"),
274 name,
275 WEXITSTATUS(status));
276 if (WEXITSTATUS(status) != 0) {
277 ok = FALSE;
278 }
279 } else if (WIFSIGNALED(status)) {
280 SCLog(TRUE, LOG_DEBUG,
281 CFSTR(" target=%@: terminated w/signal = %d"),
282 name,
283 WTERMSIG(status));
284 ok = FALSE;
285 } else {
286 SCLog(TRUE, LOG_DEBUG,
287 CFSTR(" target=%@: exit status = %d"),
288 name,
289 status);
290 ok = FALSE;
291 }
292
293 if (!ok) {
294 if (CFDictionaryContainsKey(target->dict, CFSTR("postName"))) {
295 CFDictionaryRef oldDict = target->dict;
296 CFMutableDictionaryRef newDict = CFDictionaryCreateMutableCopy(NULL, 0, oldDict);
297
298 /*
299 * if this target specifies both a BSD notification and
300 * a script to be executed then we want to continue to
301 * post the BSD notifications (and not execute the
302 * script). As such, remove the script reference from
303 * the dictionary.
304 */
305 CFDictionaryRemoveValue(newDict, CFSTR("execCommand"));
306 CFDictionaryRemoveValue(newDict, CFSTR("execGID"));
307 CFDictionaryRemoveValue(newDict, CFSTR("execUID"));
308 CFDictionaryRemoveValue(newDict, CFSTR("changedKeysAsArguments"));
309 target->dict = newDict;
310 CFRelease(oldDict);
311 } else {
312 /*
313 * If the target action can't be performed this time then
314 * there's not much point in trying again. As such, I close
315 * the session and the kickee target released.
316 */
317 cleanupKicker(target);
318 target = NULL;
319 }
320 }
321 if (target != NULL && target->needsKick) {
322 target->needsKick = FALSE;
323 booter(target);
324 }
325
326 return;
327 }
328
329
330 static void
331 kicker(SCDynamicStoreRef store, CFArrayRef changedKeys, void *arg)
332 {
333 CFIndex i;
334 CFIndex n = CFArrayGetCount(changedKeys);
335 kickeeRef target = (kickeeRef)arg;
336
337 /*
338 * Start a new kicker. If a kicker was already active then flag
339 * the need for a second kick after the active one completes.
340 */
341
342 /* create (or add to) the full list of keys that have changed */
343 if (!target->changedKeys) {
344 target->changedKeys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
345 }
346 for (i = 0; i < n; i++) {
347 CFStringRef key = CFArrayGetValueAtIndex(changedKeys, i);
348
349 if (!CFArrayContainsValue(target->changedKeys,
350 CFRangeMake(0, CFArrayGetCount(target->changedKeys)),
351 key)) {
352 CFArrayAppendValue(target->changedKeys, key);
353 }
354 }
355
356 /*
357 * let 'er rip.
358 */
359 booter(target);
360
361 return;
362 }
363
364
365 /*
366 * startKicker()
367 *
368 * The first argument is a dictionary representing the keys
369 * which need to be monitored for a given "target" and what
370 * action should be taken if a change in one of those keys
371 * is detected.
372 */
373 static void
374 startKicker(const void *value, void *context)
375 {
376 CFMutableStringRef name;
377 CFArrayRef keys;
378 CFArrayRef patterns;
379 kickeeRef target = CFAllocatorAllocate(NULL, sizeof(kickee), 0);
380 SCDynamicStoreContext targetContext = { 0, (void *)target, NULL, NULL, NULL };
381
382 target->active = FALSE;
383 target->needsKick = FALSE;
384 target->dict = CFRetain((CFDictionaryRef)value);
385 target->store = NULL;
386 target->rl = NULL;
387 target->rls = NULL;
388 target->changedKeys = NULL;
389
390 name = CFStringCreateMutableCopy(NULL,
391 0,
392 CFDictionaryGetValue(target->dict, CFSTR("name")));
393 SCLog(TRUE, LOG_DEBUG, CFSTR("Starting kicker for %@"), name);
394
395 CFStringAppend(name, CFSTR(" \"Kicker\""));
396 target->store = SCDynamicStoreCreate(NULL, name, kicker, &targetContext);
397 CFRelease(name);
398 if (!target->store) {
399 SCLog(TRUE,
400 LOG_NOTICE,
401 CFSTR("SCDynamicStoreCreate() failed: %s"),
402 SCErrorString(SCError()));
403 goto error;
404 }
405
406 keys = isA_CFArray(CFDictionaryGetValue(target->dict, CFSTR("keys")));
407 patterns = isA_CFArray(CFDictionaryGetValue(target->dict, CFSTR("regexKeys")));
408 if (!SCDynamicStoreSetNotificationKeys(target->store, keys, patterns)) {
409 SCLog(TRUE,
410 LOG_NOTICE,
411 CFSTR("SCDynamicStoreSetNotifications() failed: %s"),
412 SCErrorString(SCError()));
413 goto error;
414 }
415
416 target->rl = CFRunLoopGetCurrent();
417 target->rls = SCDynamicStoreCreateRunLoopSource(NULL, target->store, 0);
418 if (!target->rls) {
419 SCLog(TRUE,
420 LOG_NOTICE,
421 CFSTR("SCDynamicStoreCreateRunLoopSource() failed: %s"),
422 SCErrorString(SCError()));
423 goto error;
424 }
425
426 CFRunLoopAddSource(target->rl, target->rls, kCFRunLoopDefaultMode);
427 return;
428
429 error :
430
431 CFRelease(target->dict);
432 if (target->store) CFRelease(target->store);
433 CFAllocatorDeallocate(NULL, target);
434 return;
435 }
436
437
438 static CFArrayRef
439 getTargets(CFBundleRef bundle)
440 {
441 Boolean ok;
442 CFArrayRef targets; /* The array of dictionaries
443 representing targets with
444 a "kick me" sign posted on
445 their backs. */
446 CFURLRef url;
447 CFStringRef xmlError;
448 CFDataRef xmlTargets = NULL;
449
450 /* locate the Kicker targets */
451 url = CFBundleCopyResourceURL(bundle, CFSTR("Kicker"), CFSTR("xml"), NULL);
452 if (url == NULL) {
453 return NULL;
454 }
455
456 /* read the resource data */
457 ok = CFURLCreateDataAndPropertiesFromResource(NULL, url, &xmlTargets, NULL, NULL, NULL);
458 CFRelease(url);
459 if (!ok || (xmlTargets == NULL)) {
460 return NULL;
461 }
462
463 /* convert the XML data into a property list */
464 targets = CFPropertyListCreateFromXMLData(NULL,
465 xmlTargets,
466 kCFPropertyListImmutable,
467 &xmlError);
468 CFRelease(xmlTargets);
469 if (targets == NULL) {
470 if (xmlError != NULL) {
471 SCLog(TRUE, LOG_DEBUG, CFSTR("getTargets(): %@"), xmlError);
472 CFRelease(xmlError);
473 }
474 return NULL;
475 }
476
477 if (!isA_CFArray(targets)) {
478 CFRelease(targets);
479 targets = NULL;
480 }
481
482 return targets;
483 }
484
485
486 __private_extern__
487 void
488 load_Kicker(CFBundleRef bundle, Boolean bundleVerbose)
489 {
490 CFArrayRef targets; /* The array of dictionaries representing targets
491 * with a "kick me" sign posted on their backs.*/
492
493 if (bundleVerbose) {
494 _verbose = TRUE;
495 }
496
497 SCLog(_verbose, LOG_DEBUG, CFSTR("load() called"));
498 SCLog(_verbose, LOG_DEBUG, CFSTR(" bundle ID = %@"), CFBundleGetIdentifier(bundle));
499
500 /* get the bundle's URL */
501 myBundleURL = CFBundleCopyBundleURL(bundle);
502 if (myBundleURL == NULL) {
503 return;
504 }
505
506 /* get the targets */
507 targets = getTargets(bundle);
508 if (targets == NULL) {
509 /* if nothing to do */
510 CFRelease(myBundleURL);
511 return;
512 }
513
514 /* start a kicker for each target */
515 CFArrayApplyFunction(targets,
516 CFRangeMake(0, CFArrayGetCount(targets)),
517 startKicker,
518 NULL);
519 CFRelease(targets);
520
521 return;
522 }
523
524 #ifdef MAIN
525 int
526 main(int argc, char * const argv[])
527 {
528 _sc_log = FALSE;
529 _sc_verbose = (argc > 1) ? TRUE : FALSE;
530
531 load_Kicker(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
532 CFRunLoopRun();
533 /* not reached */
534 exit(0);
535 return 0;
536 }
537 #endif