]> git.saurik.com Git - apple/configd.git/blob - SCMonitor/monitor.c
configd-1061.141.1.tar.gz
[apple/configd.git] / SCMonitor / monitor.c
1 /*
2 * Copyright (c) 2007-2018 Apple 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 * October 24, 2007 Allan Nathanson <ajn@apple.com>
28 * - initial revision
29 */
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <CoreFoundation/CoreFoundation.h>
35
36 #define SC_LOG_HANDLE __log_SCMonitor
37 #define SC_LOG_HANDLE_TYPE static
38 #include <SystemConfiguration/SystemConfiguration.h>
39 #include <SystemConfiguration/SCPrivate.h>
40
41 #include <IOKit/IOKitLib.h>
42 #include <IOKit/IOKitKeysPrivate.h>
43 #include <IOKit/IOMessage.h>
44 #include <ApplicationServices/ApplicationServices.h>
45 #include "UserEventAgentInterface.h"
46
47 #define MY_BUNDLE_ID "com.apple.SystemConfiguration.SCMonitor"
48 #define MY_ICON_PATH "/System/Library/PreferencePanes/Network.prefPane/Contents/Resources/Network.icns"
49
50 #define NETWORK_PREF_APP "/System/Library/PreferencePanes/Network.prefPane"
51 #define NETWORK_PREF_CMD "New Interface"
52
53 /*
54 * The following keys/values control the actions taken when a new interface
55 * has been detected and whether we interact with the user.
56 *
57 * The keys/values can be provided globally (in SCMonitor.plugin's Info.plist
58 * file) or per-inteface in the IORegistry.
59 *
60 * For the "New Interface Detected Action" key we define the following [CFString]
61 * values :
62 *
63 * "None" No action, ignore this interface.
64 * "Prompt" Post a "new interface detected" notification to the user.
65 * "Configure" Automatically configure this interface without any
66 * intervention.
67 *
68 * Note: automatic configuration may not be possible if the logged in user
69 * is NOT "root" (eUID==0) or if the authorization right that governs
70 * SCHelper write operations (kSCPreferencesAuthorizationRight_write)
71 * is not currently available.
72 *
73 * An [older] "User Intervention" key is also supported. That CFBoolean
74 * key, if present and TRUE, implies "Configure" configuration of the
75 * interface without intervention.
76 */
77
78 typedef struct {
79 UserEventAgentInterfaceStruct *_UserEventAgentInterface;
80 CFUUIDRef _factoryID;
81 UInt32 _refCount;
82
83 Boolean debug;
84
85 CFStringRef configuration_action;
86
87 CFRunLoopSourceRef monitorRls;
88
89 IONotificationPortRef notifyPort;
90 io_iterator_t notifyIterator;
91 CFMutableArrayRef notifyNodes;
92
93 // interfaces that we already know about
94 CFMutableSetRef interfaces_known;
95
96 // interfaces that should be auto-configured (no user notification)
97 CFMutableArrayRef interfaces_configure;
98
99 // interfaces that require user notification
100 CFMutableArrayRef interfaces_prompt;
101
102 CFUserNotificationRef userNotification;
103 CFRunLoopSourceRef userRls;
104
105 AuthorizationRef authorization;
106 } MyType;
107
108 static CFMutableDictionaryRef notify_to_instance = NULL;
109
110
111 #pragma mark -
112 #pragma mark Logging
113
114
115 /*
116 * Logging
117 */
118 static os_log_t
119 __log_SCMonitor(void)
120 {
121 static os_log_t log = NULL;
122
123 if (log == NULL) {
124 log = os_log_create("com.apple.SystemConfiguration", "SCMonitor");
125 }
126
127 return log;
128 }
129
130
131 #pragma mark -
132 #pragma mark Authorization
133
134
135 static AuthorizationRef
136 getAuthorization(MyType *myInstance)
137 {
138 if (myInstance->authorization == NULL) {
139 AuthorizationFlags flags = kAuthorizationFlagDefaults;
140 OSStatus status;
141
142 status = AuthorizationCreate(NULL,
143 kAuthorizationEmptyEnvironment,
144 flags,
145 &myInstance->authorization);
146 if (status != errAuthorizationSuccess) {
147 SC_log(LOG_ERR, "AuthorizationCreate() failed: status = %d", (int)status);
148 }
149 }
150
151 return myInstance->authorization;
152 }
153
154
155 static Boolean
156 hasAuthorization(MyType *myInstance)
157 {
158 AuthorizationRef authorization;
159 Boolean isAdmin = FALSE;
160
161 authorization = getAuthorization(myInstance);
162 if (authorization != NULL) {
163 AuthorizationFlags flags = kAuthorizationFlagDefaults;
164 AuthorizationItem items[1];
165 AuthorizationRights rights;
166 OSStatus status;
167
168 items[0].name = kSCPreferencesAuthorizationRight_write;
169 items[0].value = NULL;
170 items[0].valueLength = 0;
171 items[0].flags = 0;
172
173 rights.count = sizeof(items) / sizeof(items[0]);
174 rights.items = items;
175
176 status = AuthorizationCopyRights(authorization,
177 &rights,
178 kAuthorizationEmptyEnvironment,
179 flags,
180 NULL);
181 isAdmin = (status == errAuthorizationSuccess);
182 }
183
184 return isAdmin;
185 }
186
187
188 static void
189 freeAuthorization(MyType *myInstance)
190 {
191 if (myInstance->authorization != NULL) {
192 AuthorizationFree(myInstance->authorization, kAuthorizationFlagDefaults);
193 // AuthorizationFree(myInstance->authorization, kAuthorizationFlagDestroyRights);
194 myInstance->authorization = NULL;
195 }
196
197 return;
198 }
199
200
201 #pragma mark -
202 #pragma mark New interface notification / configuration
203
204
205 static void
206 open_NetworkPrefPane(MyType *myInstance)
207 {
208 #pragma unused(myInstance)
209 AEDesc aeDesc = { typeNull, NULL };
210 CFArrayRef prefArray;
211 CFURLRef prefURL;
212 LSLaunchURLSpec prefSpec;
213 OSStatus status;
214
215 prefURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
216 CFSTR(NETWORK_PREF_APP),
217 kCFURLPOSIXPathStyle,
218 FALSE);
219 prefArray = CFArrayCreate(NULL, (const void **)&prefURL, 1, &kCFTypeArrayCallBacks);
220 CFRelease(prefURL);
221
222 status = AECreateDesc('ptru',
223 (const void *)NETWORK_PREF_CMD,
224 strlen(NETWORK_PREF_CMD),
225 &aeDesc);
226 if (status != noErr) {
227 SC_log(LOG_ERR, "AECreateDesc() failed: %d", (int)status);
228 }
229
230 prefSpec.appURL = NULL;
231 prefSpec.itemURLs = prefArray;
232 prefSpec.passThruParams = &aeDesc;
233 prefSpec.launchFlags = kLSLaunchAsync | kLSLaunchDontAddToRecents;
234 prefSpec.asyncRefCon = NULL;
235
236 status = LSOpenFromURLSpec(&prefSpec, NULL);
237 if (status != noErr) {
238 SC_log(LOG_ERR, "LSOpenFromURLSpec() failed: %d", (int)status);
239 }
240
241 CFRelease(prefArray);
242 if (aeDesc.descriptorType != typeNull) AEDisposeDesc(&aeDesc);
243 return;
244 }
245
246
247 static void
248 notify_remove(MyType *myInstance, Boolean cancel)
249 {
250 if (myInstance->interfaces_configure != NULL) {
251 CFRelease(myInstance->interfaces_configure);
252 myInstance->interfaces_configure = NULL;
253 }
254
255 if (myInstance->interfaces_prompt != NULL) {
256 CFRelease(myInstance->interfaces_prompt);
257 myInstance->interfaces_prompt = NULL;
258 }
259
260 if (myInstance->userRls != NULL) {
261 CFRunLoopSourceInvalidate(myInstance->userRls);
262 CFRelease(myInstance->userRls);
263 myInstance->userRls = NULL;
264 }
265
266 if (myInstance->userNotification != NULL) {
267 if (cancel) {
268 SInt32 status;
269
270 status = CFUserNotificationCancel(myInstance->userNotification);
271 if (status != 0) {
272 SC_log(LOG_ERR,
273 "CFUserNotificationCancel() failed, status=%d",
274 (int)status);
275 }
276 }
277 CFRelease(myInstance->userNotification);
278 myInstance->userNotification = NULL;
279 }
280
281 return;
282 }
283
284
285 static void
286 notify_reply(CFUserNotificationRef userNotification, CFOptionFlags response_flags)
287 {
288 MyType *myInstance = NULL;
289
290 // get instance for notification
291 if (notify_to_instance != NULL) {
292 myInstance = (MyType *)CFDictionaryGetValue(notify_to_instance, userNotification);
293 if (myInstance != NULL) {
294 CFDictionaryRemoveValue(notify_to_instance, userNotification);
295 if (CFDictionaryGetCount(notify_to_instance) == 0) {
296 CFRelease(notify_to_instance);
297 notify_to_instance = NULL;
298 }
299 }
300 }
301 if (myInstance == NULL) {
302 SC_log(LOG_ERR, "can't find user notification");
303 return;
304 }
305
306 // process response
307 switch (response_flags & 0x3) {
308 case kCFUserNotificationDefaultResponse:
309 // user asked to configure interface
310 open_NetworkPrefPane(myInstance);
311 break;
312 default:
313 // user cancelled
314 break;
315 }
316
317 notify_remove(myInstance, FALSE);
318 return;
319 }
320
321
322 static void
323 notify_add(MyType *myInstance)
324 {
325 CFBundleRef bundle;
326 CFMutableDictionaryRef dict = NULL;
327 SInt32 error = 0;
328 CFIndex i;
329 CFIndex n = CFArrayGetCount(myInstance->interfaces_prompt);
330 CFURLRef url = NULL;
331
332 if (myInstance->userNotification != NULL) {
333 CFMutableArrayRef save = NULL;
334
335 if (n > 0) {
336 CFRetain(myInstance->interfaces_prompt);
337 save = myInstance->interfaces_prompt;
338 }
339 notify_remove(myInstance, TRUE);
340 myInstance->interfaces_prompt = save;
341 if (n == 0) {
342 return;
343 }
344 }
345
346 dict = CFDictionaryCreateMutable(NULL,
347 0,
348 &kCFTypeDictionaryKeyCallBacks,
349 &kCFTypeDictionaryValueCallBacks);
350
351 // set localization URL
352 bundle = CFBundleGetBundleWithIdentifier(CFSTR(MY_BUNDLE_ID));
353 if (bundle != NULL) {
354 url = CFBundleCopyBundleURL(bundle);
355 }
356 #ifdef MAIN
357 if (url == NULL) {
358 url = CFURLCreateFromFileSystemRepresentation(NULL,
359 (const UInt8 *)"/System/Library/UserEventPlugins/SCMonitor.plugin",
360 strlen("/System/Library/UserEventPlugins/SCMonitor.plugin"),
361 FALSE);
362 if (bundle == NULL) {
363 bundle = CFBundleCreate(NULL, url);
364 }
365 }
366 #endif // MAIN
367 if (url != NULL) {
368 // set URL
369 CFDictionarySetValue(dict, kCFUserNotificationLocalizationURLKey, url);
370 CFRelease(url);
371 } else {
372 SC_log(LOG_ERR, "can't find bundle");
373 goto done;
374 }
375
376 // set icon URL
377 url = CFURLCreateFromFileSystemRepresentation(NULL,
378 (const UInt8 *)MY_ICON_PATH,
379 strlen(MY_ICON_PATH),
380 FALSE);
381 if (url != NULL) {
382 CFDictionarySetValue(dict, kCFUserNotificationIconURLKey, url);
383 CFRelease(url);
384 }
385
386 // header
387 CFDictionarySetValue(dict,
388 kCFUserNotificationAlertHeaderKey,
389 (n == 1) ? CFSTR("HEADER_1") : CFSTR("HEADER_N"));
390
391 // message
392 if (n == 1) {
393 CFStringRef format;
394 SCNetworkInterfaceRef interface;
395 CFStringRef message;
396 CFStringRef name;
397
398 #define MESSAGE_1 "The “%@” network interface has not been set up. To set up this interface, use Network Preferences."
399
400 format = CFBundleCopyLocalizedString(bundle,
401 CFSTR("MESSAGE_1"),
402 CFSTR(MESSAGE_1),
403 NULL);
404 interface = CFArrayGetValueAtIndex(myInstance->interfaces_prompt, 0);
405 name = SCNetworkInterfaceGetLocalizedDisplayName(interface);
406 message = CFStringCreateWithFormat(NULL, NULL, format, name);
407 CFDictionarySetValue(dict, kCFUserNotificationAlertMessageKey, message);
408 CFRelease(message);
409 CFRelease(format);
410 } else {
411 CFMutableArrayRef message;
412
413 message = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
414 CFArrayAppendValue(message, CFSTR("MESSAGE_SN"));
415 for (i = 0; i < n; i++) {
416 SCNetworkInterfaceRef interface;
417 CFStringRef name;
418 CFStringRef str;
419
420 interface = CFArrayGetValueAtIndex(myInstance->interfaces_prompt, i);
421 name = SCNetworkInterfaceGetLocalizedDisplayName(interface);
422 str = CFStringCreateWithFormat(NULL, NULL, CFSTR("\r\t%@"), name);
423 CFArrayAppendValue(message, str);
424 CFRelease(str);
425 }
426 CFArrayAppendValue(message, CFSTR("MESSAGE_EN"));
427 CFDictionarySetValue(dict, kCFUserNotificationAlertMessageKey, message);
428 CFRelease(message);
429 }
430
431 // button titles
432 CFDictionaryAddValue(dict, kCFUserNotificationDefaultButtonTitleKey, CFSTR("OPEN_NP"));
433 CFDictionaryAddValue(dict, kCFUserNotificationAlternateButtonTitleKey, CFSTR("CANCEL"));
434
435 // create and post notification
436 myInstance->userNotification = CFUserNotificationCreate(NULL,
437 0,
438 kCFUserNotificationNoteAlertLevel,
439 &error,
440 dict);
441 if (myInstance->userNotification == NULL) {
442 SC_log(LOG_ERR, "CFUserNotificationCreate() failed: %d", (int)error);
443 goto done;
444 }
445
446 // establish callback
447 myInstance->userRls = CFUserNotificationCreateRunLoopSource(NULL,
448 myInstance->userNotification,
449 notify_reply,
450 0);
451 if (myInstance->userRls == NULL) {
452 SC_log(LOG_ERR, "CFUserNotificationCreateRunLoopSource() failed");
453 CFRelease(myInstance->userNotification);
454 myInstance->userNotification = NULL;
455 goto done;
456 }
457 CFRunLoopAddSource(CFRunLoopGetCurrent(), myInstance->userRls, kCFRunLoopDefaultMode);
458
459 // add instance for notification
460 if (notify_to_instance == NULL) {
461 notify_to_instance = CFDictionaryCreateMutable(NULL,
462 0,
463 &kCFTypeDictionaryKeyCallBacks,
464 NULL); // no retain/release/... for values
465 }
466 CFDictionarySetValue(notify_to_instance, myInstance->userNotification, myInstance);
467
468 done :
469
470 if (dict != NULL) CFRelease(dict);
471 return;
472 }
473
474
475 static void
476 notify_configure(MyType *myInstance)
477 {
478 CFIndex i;
479 CFIndex n = CFArrayGetCount(myInstance->interfaces_configure);
480 Boolean ok;
481 SCPreferencesRef prefs = NULL;
482 SCNetworkSetRef set = NULL;
483
484 if (geteuid() == 0) {
485 prefs = SCPreferencesCreate(NULL, CFSTR("SCMonitor"), NULL);
486 } else {
487 AuthorizationRef authorization;
488
489 authorization = getAuthorization(myInstance);
490 if (authorization == NULL) {
491 return;
492 }
493
494 prefs = SCPreferencesCreateWithAuthorization(NULL, CFSTR("SCMonitor"), NULL, authorization);
495 }
496
497 set = SCNetworkSetCopyCurrent(prefs);
498 if (set == NULL) {
499 // if no "current" set, create new/default ("Automatic") set
500 set = _SCNetworkSetCreateDefault(prefs);
501 if (set == NULL) {
502 goto done;
503 }
504 SC_log(LOG_DEBUG, "added new \"default\" set");
505 }
506
507 for (i = 0; i < n; i++) {
508 SCNetworkInterfaceRef interface;
509
510 interface = CFArrayGetValueAtIndex(myInstance->interfaces_configure, i);
511 ok = SCNetworkSetEstablishDefaultInterfaceConfiguration(set, interface);
512 if (ok) {
513 CFStringRef name;
514
515 name = SCNetworkInterfaceGetLocalizedDisplayName(interface);
516 SC_log(LOG_NOTICE, "add/update service for %@", name);
517 }
518 }
519
520 ok = SCPreferencesCommitChanges(prefs);
521 if (!ok) {
522 SC_log(LOG_ERR,
523 "SCPreferencesCommitChanges() failed: %s",
524 SCErrorString(SCError()));
525 goto done;
526 }
527
528 ok = SCPreferencesApplyChanges(prefs);
529 if (!ok) {
530 SC_log(LOG_ERR,
531 "SCPreferencesApplyChanges() failed: %s",
532 SCErrorString(SCError()));
533 goto done;
534 }
535
536 done :
537
538 if (set != NULL) {
539 CFRelease(set);
540 set = NULL;
541 }
542
543 if (prefs != NULL) {
544 CFRelease(prefs);
545 prefs = NULL;
546 }
547
548 CFRelease(myInstance->interfaces_configure);
549 myInstance->interfaces_configure = NULL;
550
551 return;
552 }
553
554
555 #pragma mark -
556
557 static Boolean
558 onConsole()
559 {
560 CFArrayRef console_sessions;
561 Boolean on = FALSE;
562 io_registry_entry_t root;
563 uid_t uid = geteuid();
564
565 root = IORegistryGetRootEntry(kIOMasterPortDefault);
566 console_sessions = IORegistryEntryCreateCFProperty(root,
567 CFSTR(kIOConsoleUsersKey),
568 NULL,
569 0);
570 if (console_sessions != NULL) {
571 CFIndex n;
572
573 n = isA_CFArray(console_sessions) ? CFArrayGetCount(console_sessions) : 0;
574 for (CFIndex i = 0; i < n; i++) {
575 CFBooleanRef bVal;
576 CFDictionaryRef session;
577 uint64_t sessionUID;
578 CFNumberRef val;
579
580 session = CFArrayGetValueAtIndex(console_sessions, i);
581 if (!isA_CFDictionary(session)) {
582 // if not dictionary
583 continue;
584 }
585
586 if (!CFDictionaryGetValueIfPresent(session,
587 CFSTR(kIOConsoleSessionUIDKey),
588 (const void **)&val) ||
589 !isA_CFNumber(val) ||
590 !CFNumberGetValue(val, kCFNumberSInt64Type, (void *)&sessionUID) ||
591 (uid != sessionUID)) {
592 // if not my session
593 continue;
594 }
595
596 if (CFDictionaryGetValueIfPresent(session,
597 CFSTR(kIOConsoleSessionOnConsoleKey),
598 (const void **)&bVal) &&
599 isA_CFBoolean(bVal) &&
600 CFBooleanGetValue(bVal)) {
601 // if "on console" session
602 on = TRUE;
603 }
604
605 break;
606 }
607
608 CFRelease(console_sessions);
609 }
610 IOObjectRelease(root);
611
612 return on;
613 }
614
615
616 #pragma mark -
617
618
619 // configure ONLY IF authorized
620 #define kSCNetworkInterfaceConfigurationActionValueConfigureAuthorized CFSTR("Configure-Authorized")
621
622
623 static void
624 updateInterfaceList(MyType *myInstance)
625 {
626 Boolean changed = FALSE;
627 CFIndex i;
628 CFArrayRef interfaces;
629 CFMutableSetRef interfaces_old = NULL;
630 CFIndex n;
631 SCPreferencesRef prefs;
632 SCNetworkSetRef set = NULL;
633
634 if (!onConsole()) {
635 return;
636 }
637
638 prefs = SCPreferencesCreate(NULL, CFSTR("SCMonitor"), NULL);
639 if (prefs == NULL) {
640 return;
641 }
642
643 set = SCNetworkSetCopyCurrent(prefs);
644 if (set == NULL) {
645 // if no "current" set, create new/default ("Automatic") set
646 set = _SCNetworkSetCreateDefault(prefs);
647 if (set == NULL) {
648 goto done;
649 }
650 }
651
652 interfaces_old = CFSetCreateMutableCopy(NULL, 0, myInstance->interfaces_known);
653
654 interfaces = _SCNetworkInterfaceCopyAllWithPreferences(prefs);
655 if (interfaces != NULL) {
656 n = CFArrayGetCount(interfaces);
657 for (i = 0; i < n; i++) {
658 SCNetworkInterfaceRef interface;
659 Boolean ok;
660
661 interface = CFArrayGetValueAtIndex(interfaces, i);
662
663 // track new vs. old (removed) interfaces
664 CFSetRemoveValue(interfaces_old, interface);
665 if (CFSetContainsValue(myInstance->interfaces_known, interface)) {
666 // if we already know about this interface
667 continue;
668 }
669 CFSetAddValue(myInstance->interfaces_known, interface);
670 changed = TRUE;
671
672 ok = SCNetworkSetEstablishDefaultInterfaceConfiguration(set, interface);
673 if (ok) {
674 CFStringRef action;
675
676 // this is a *new* interface
677
678 action = _SCNetworkInterfaceGetConfigurationAction(interface);
679 if (action == NULL) {
680 // if no per-interface action, use [global] default
681 action = myInstance->configuration_action;
682 }
683 if ((action == NULL) ||
684 (!CFEqual(action, kSCNetworkInterfaceConfigurationActionValueNone) &&
685 !CFEqual(action, kSCNetworkInterfaceConfigurationActionValueConfigure))) {
686 if (_SCNetworkInterfaceIsBuiltin(interface)) {
687 // if built-in interface
688 action = kSCNetworkInterfaceConfigurationActionValueConfigureAuthorized;
689 } else {
690 action = kSCNetworkInterfaceConfigurationActionValuePrompt;
691 }
692 }
693
694 if (CFEqual(action, kSCNetworkInterfaceConfigurationActionValueNone)) {
695 continue;
696 } else if (CFEqual(action, kSCNetworkInterfaceConfigurationActionValueConfigure)) {
697 // configure automatically (without user intervention)
698 if (myInstance->interfaces_configure == NULL) {
699 myInstance->interfaces_configure = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
700 }
701 CFArrayAppendValue(myInstance->interfaces_configure, interface);
702 } else if (hasAuthorization(myInstance)) {
703 // if we already have the "admin" (kSCPreferencesAuthorizationRight_write)
704 // right, configure automatically (without user intervention)
705 if (myInstance->interfaces_configure == NULL) {
706 myInstance->interfaces_configure = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
707 }
708 CFArrayAppendValue(myInstance->interfaces_configure, interface);
709 } else if (!CFEqual(action, kSCNetworkInterfaceConfigurationActionValueConfigureAuthorized)) {
710 // notify user
711 if (myInstance->interfaces_prompt == NULL) {
712 myInstance->interfaces_prompt = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
713 }
714 CFArrayAppendValue(myInstance->interfaces_prompt, interface);
715 }
716 }
717 }
718
719 CFRelease(interfaces);
720 }
721
722 // remove any posted notifications for network interfaces that have been removed
723 n = CFSetGetCount(interfaces_old);
724 if (n > 0) {
725 const void * paths_q[32];
726 const void ** paths = paths_q;
727
728 if (n > (CFIndex)(sizeof(paths_q) / sizeof(CFTypeRef)))
729 paths = CFAllocatorAllocate(NULL, n * sizeof(CFTypeRef), 0);
730 CFSetGetValues(interfaces_old, paths);
731 for (i = 0; i < n; i++) {
732 if (myInstance->interfaces_prompt != NULL) {
733 CFIndex j;
734
735 j = CFArrayGetCount(myInstance->interfaces_prompt);
736 while (j > 0) {
737 SCNetworkInterfaceRef interface;
738
739 j--;
740 interface = CFArrayGetValueAtIndex(myInstance->interfaces_prompt, j);
741 if (CFEqual(interface, paths[i])) {
742 // if we have previously posted a notification
743 // for this no-longer-present interface
744 CFArrayRemoveValueAtIndex(myInstance->interfaces_prompt, j);
745 changed = TRUE;
746 }
747 }
748 }
749
750 CFSetRemoveValue(myInstance->interfaces_known, paths[i]);
751 }
752 if (paths != paths_q) CFAllocatorDeallocate(NULL, paths);
753 }
754
755 done :
756
757 if (changed) {
758 if (myInstance->interfaces_configure != NULL) {
759 // if we have network services to configure automatically
760 notify_configure(myInstance);
761 }
762
763 if (myInstance->interfaces_prompt != NULL) {
764 // if we have network services that require user intervention
765 // post notification for new interfaces
766 notify_add(myInstance);
767 }
768 }
769
770 if (interfaces_old != NULL) CFRelease(interfaces_old);
771 if (set != NULL) CFRelease(set);
772 CFRelease(prefs);
773 return;
774 }
775
776
777 #pragma mark -
778 #pragma mark Watch for new [network] interfaces
779
780
781 static void
782 update_lan(SCDynamicStoreRef store, CFArrayRef changes, void * arg)
783 {
784 #pragma unused(store)
785 #pragma unused(changes)
786 MyType *myInstance = (MyType *)arg;
787
788 updateInterfaceList(myInstance);
789 return;
790 }
791
792
793 static void
794 watcher_add_lan(MyType *myInstance)
795 {
796 SCDynamicStoreContext context = { 0, (void *)myInstance, NULL, NULL, NULL };
797 CFStringRef key;
798 CFArrayRef keys;
799 SCDynamicStoreRef store;
800
801 store = SCDynamicStoreCreate(NULL, CFSTR("SCMonitor"), update_lan, &context);
802 if (store == NULL) {
803 SC_log(LOG_ERR,
804 "SCDynamicStoreCreate() failed: %s",
805 SCErrorString(SCError()));
806 return;
807 }
808
809 key = SCDynamicStoreKeyCreateNetworkInterface(NULL, kSCDynamicStoreDomainState);
810
811 // watch for changes to the list of network interfaces
812 keys = CFArrayCreate(NULL, (const void **)&key, 1, &kCFTypeArrayCallBacks);
813 SCDynamicStoreSetNotificationKeys(store, keys, NULL);
814 CFRelease(keys);
815 myInstance->monitorRls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
816 CFRunLoopAddSource(CFRunLoopGetCurrent(),
817 myInstance->monitorRls,
818 kCFRunLoopDefaultMode);
819
820 // check if we already have the "admin" (kSCPreferencesAuthorizationRight_write)
821 // right. If so, we can automatically configure (without user intervention) any
822 // "new" network interfaces that are present at login (e.g. a USB ethernet
823 // dongle that was plugged in before login).
824 if (!hasAuthorization(myInstance)) {
825 CFDictionaryRef dict;
826
827 // ... and if we don't have the right then we populate the list of
828 // known interfaces with those already named so that we avoid any
829 // login prompts (that the user might have no choice but to dismiss)
830 dict = SCDynamicStoreCopyValue(store, key);
831 if (dict != NULL) {
832 if (isA_CFDictionary(dict)) {
833 CFIndex i;
834 CFArrayRef interfaces;
835 CFIndex n;
836
837 interfaces = CFDictionaryGetValue(dict, kSCPropNetInterfaces);
838 n = isA_CFArray(interfaces) ? CFArrayGetCount(interfaces) : 0;
839 for (i = 0; i < n; i++) {
840 CFStringRef bsdName;
841
842 bsdName = CFArrayGetValueAtIndex(interfaces, i);
843 if (isA_CFString(bsdName)) {
844 SCNetworkInterfaceRef interface;
845
846 interface = _SCNetworkInterfaceCreateWithBSDName(NULL, bsdName, kIncludeNoVirtualInterfaces);
847 if (interface != NULL) {
848 CFSetAddValue(myInstance->interfaces_known, interface);
849 CFRelease(interface);
850 }
851 }
852 }
853 }
854
855 CFRelease(dict);
856 }
857 }
858
859 CFRelease(key);
860 CFRelease(store);
861 return;
862 }
863
864
865 static void
866 watcher_remove_lan(MyType *myInstance)
867 {
868 if (myInstance->monitorRls != NULL) {
869 CFRunLoopSourceInvalidate(myInstance->monitorRls);
870 CFRelease(myInstance->monitorRls);
871 myInstance->monitorRls = NULL;
872 }
873
874 return;
875 }
876
877
878 #pragma mark -
879
880
881 typedef struct {
882 io_registry_entry_t interface;
883 io_registry_entry_t interface_node;
884 MyType *myInstance;
885 io_object_t notification;
886 } MyNode;
887
888
889 static void
890 add_node_watcher(MyType *myInstance, io_registry_entry_t node, io_registry_entry_t interface);
891
892
893 static void
894 update_node(void *refCon, io_service_t service, natural_t messageType, void *messageArgument)
895 {
896 #pragma unused(messageArgument)
897 CFIndex i;
898 CFDataRef myData = (CFDataRef)refCon;
899 MyType *myInstance;
900 MyNode *myNode;
901
902 /* ALIGN: CF aligns to at least >8 bytes */
903 myNode = (MyNode *)(void *)CFDataGetBytePtr(myData);
904 myInstance = myNode->myInstance;
905
906 switch (messageType) {
907 case kIOMessageServicePropertyChange : {
908 Boolean initializing = FALSE;
909 SCNetworkInterfaceRef interface;
910 CFTypeRef val;
911
912 if (myNode->interface == MACH_PORT_NULL) {
913 // if we are not watching the "Initializing" property
914 return;
915 }
916
917 val = IORegistryEntryCreateCFProperty(service, kSCNetworkInterfaceInitializingKey, NULL, 0);
918 if (val != NULL) {
919 initializing = (isA_CFBoolean(val) && CFBooleanGetValue(val));
920 CFRelease(val);
921 if (initializing) {
922 // if initialization not complete, keep watching
923 return;
924 }
925 }
926
927 // node is ready
928 interface = _SCNetworkInterfaceCreateWithIONetworkInterfaceObject(myNode->interface);
929 if (interface != NULL) {
930 CFRelease(interface);
931
932 // watch interface (to see when/if it's removed)
933 add_node_watcher(myInstance, myNode->interface, MACH_PORT_NULL);
934 }
935 break;
936 }
937
938 case kIOMessageServiceIsTerminated :
939 break;
940
941 default :
942 return;
943 }
944
945 // remove no-longer-needed notification
946 if (myNode->interface != myNode->interface_node) {
947 IOObjectRelease(myNode->interface_node);
948 }
949 if (myNode->interface != MACH_PORT_NULL) {
950 IOObjectRelease(myNode->interface);
951 }
952 IOObjectRelease(myNode->notification);
953 i = CFArrayGetFirstIndexOfValue(myInstance->notifyNodes,
954 CFRangeMake(0, CFArrayGetCount(myInstance->notifyNodes)),
955 myData);
956 if (i != kCFNotFound) {
957 CFArrayRemoveValueAtIndex(myInstance->notifyNodes, i);
958 if (CFArrayGetCount(myInstance->notifyNodes) == 0) {
959 CFRelease(myInstance->notifyNodes);
960 myInstance->notifyNodes = NULL;
961 }
962 }
963
964 updateInterfaceList(myInstance);
965 return;
966 }
967
968
969 static void
970 add_node_watcher(MyType *myInstance, io_registry_entry_t node, io_registry_entry_t interface)
971 {
972 kern_return_t kr;
973 CFMutableDataRef myData;
974 MyNode *myNode;
975
976 // wait for initialization to complete
977 myData = CFDataCreateMutable(NULL, sizeof(MyNode));
978 CFDataSetLength(myData, sizeof(MyNode));
979
980 /* ALIGN: CF aligns to at least >8 bytes */
981 myNode = (MyNode *)(void *)CFDataGetBytePtr(myData);
982
983 memset(myNode, 0, sizeof(MyNode));
984 myNode->interface = interface;
985 if (myNode->interface != MACH_PORT_NULL) {
986 IOObjectRetain(myNode->interface);
987 }
988 myNode->interface_node = (interface == MACH_PORT_NULL) ? node : interface;
989 if (myNode->interface != myNode->interface_node) {
990 IOObjectRetain(myNode->interface_node);
991 }
992 myNode->myInstance = myInstance;
993 myNode->notification = MACH_PORT_NULL;
994
995 kr = IOServiceAddInterestNotification(myInstance->notifyPort, // IONotificationPortRef
996 node, // io_service_t
997 kIOGeneralInterest, // interestType
998 update_node, // IOServiceInterestCallback
999 (void *)myData, // refCon
1000 &myNode->notification); // notification
1001 if (kr == KERN_SUCCESS) {
1002 if (myInstance->notifyNodes == NULL) {
1003 myInstance->notifyNodes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
1004 }
1005 CFArrayAppendValue(myInstance->notifyNodes, myData);
1006 } else {
1007 SC_log(LOG_ERR,
1008 "add_init_watcher IOServiceAddInterestNotification() failed, kr = 0x%x",
1009 kr);
1010 }
1011 CFRelease(myData);
1012 }
1013
1014
1015 static void
1016 add_init_watcher(MyType *myInstance, io_registry_entry_t interface)
1017 {
1018 kern_return_t kr;
1019 io_registry_entry_t node = interface;
1020 CFTypeRef val = NULL;
1021
1022 while (node != MACH_PORT_NULL) {
1023 io_registry_entry_t parent;
1024
1025 val = IORegistryEntryCreateCFProperty(node, kSCNetworkInterfaceHiddenPortKey, NULL, 0);
1026 if (val != NULL) {
1027 CFRelease(val);
1028 val = NULL;
1029 break;
1030 }
1031
1032 val = IORegistryEntryCreateCFProperty(node, kSCNetworkInterfaceInitializingKey, NULL, 0);
1033 if (val != NULL) {
1034 break;
1035 }
1036
1037 parent = MACH_PORT_NULL;
1038 kr = IORegistryEntryGetParentEntry(node, kIOServicePlane, &parent);
1039 switch (kr) {
1040 case kIOReturnSuccess : // if we have a parent node
1041 case kIOReturnNoDevice : // if we have hit the root node
1042 break;
1043 default :
1044 SC_log(LOG_ERR, "add_init_watcher IORegistryEntryGetParentEntry() failed, kr = 0x%x", kr);
1045 break;
1046 }
1047 if (node != interface) {
1048 IOObjectRelease(node);
1049 }
1050 node = parent;
1051 }
1052
1053 if (val != NULL) {
1054 if (isA_CFBoolean(val) && CFBooleanGetValue(val)) {
1055 // watch the "Initializing" node
1056 add_node_watcher(myInstance, node, interface);
1057 }
1058
1059 CFRelease(val);
1060 }
1061
1062 if ((node != MACH_PORT_NULL) && (node != interface)) {
1063 IOObjectRelease(node);
1064 }
1065
1066 return;
1067 }
1068
1069
1070 static void
1071 update_serial(void *refcon, io_iterator_t iter)
1072 {
1073 MyType *myInstance = (MyType *)refcon;
1074 io_registry_entry_t obj;
1075
1076 while ((obj = IOIteratorNext(iter)) != MACH_PORT_NULL) {
1077 SCNetworkInterfaceRef interface;
1078
1079 interface = _SCNetworkInterfaceCreateWithIONetworkInterfaceObject(obj);
1080 if (interface != NULL) {
1081 CFRelease(interface);
1082
1083 // watch interface (to see when/if it's removed)
1084 add_node_watcher(myInstance, obj, MACH_PORT_NULL);
1085 } else {
1086 // check interface, watch if initializing
1087 add_init_watcher(myInstance, obj);
1088 }
1089
1090 IOObjectRelease(obj);
1091 }
1092
1093 return;
1094 }
1095
1096
1097 static void
1098 update_serial_nodes(void *refcon, io_iterator_t iter)
1099 {
1100 MyType *myInstance = (MyType *)refcon;
1101
1102 update_serial(refcon, iter);
1103 updateInterfaceList(myInstance);
1104 }
1105
1106
1107 static void
1108 watcher_add_serial(MyType *myInstance)
1109 {
1110 kern_return_t kr;
1111
1112 myInstance->notifyPort = IONotificationPortCreate(kIOMasterPortDefault);
1113 if (myInstance->notifyPort == NULL) {
1114 SC_log(LOG_ERR, "IONotificationPortCreate failed");
1115 return;
1116 }
1117
1118 // watch for the introduction of new network serial devices
1119 kr = IOServiceAddMatchingNotification(myInstance->notifyPort,
1120 kIOFirstMatchNotification,
1121 IOServiceMatching("IOSerialBSDClient"),
1122 &update_serial_nodes,
1123 (void *)myInstance, // refCon
1124 &myInstance->notifyIterator); // notification
1125 if (kr != KERN_SUCCESS) {
1126 SC_log(LOG_ERR, "SCMonitor : IOServiceAddMatchingNotification returned 0x%x", kr);
1127 return;
1128 }
1129
1130 myInstance->notifyNodes = NULL;
1131
1132 // Get the current list of matches and arm the notification for
1133 // future interface arrivals.
1134 update_serial((void *)myInstance, myInstance->notifyIterator);
1135
1136 if (myInstance->notifyNodes != NULL) {
1137 // if we have any serial nodes, check if we already have the
1138 // "admin" (kSCPreferencesAuthorizationRight_write) right. If
1139 // so, we can automatically configure (without user intervention)
1140 // any "new" network interfaces that are present at login (e.g. a
1141 // USB modem that was plugged in before login).
1142
1143 if (!hasAuthorization(myInstance)) {
1144 CFIndex i;
1145 CFIndex n = CFArrayGetCount(myInstance->notifyNodes);
1146
1147 // ... and if we don't have the right then we populate the list of
1148 // known interfaces with those already named so that we avoid any
1149 // login prompts (that the user might have no choice but to dismiss)
1150
1151 for (i = 0; i < n; i++) {
1152 SCNetworkInterfaceRef interface;
1153 CFDataRef myData;
1154 MyNode *myNode;
1155
1156 myData = CFArrayGetValueAtIndex(myInstance->notifyNodes, i);
1157
1158 /* ALIGN: CF aligns to at least >8 bytes */
1159 myNode = (MyNode *)(void *)CFDataGetBytePtr(myData);
1160
1161 interface = _SCNetworkInterfaceCreateWithIONetworkInterfaceObject(myNode->interface_node);
1162 if (interface != NULL) {
1163 CFSetAddValue(myInstance->interfaces_known, interface);
1164 CFRelease(interface);
1165 }
1166 }
1167 }
1168 }
1169
1170 // and keep watching
1171 CFRunLoopAddSource(CFRunLoopGetCurrent(),
1172 IONotificationPortGetRunLoopSource(myInstance->notifyPort),
1173 kCFRunLoopDefaultMode);
1174 return;
1175 }
1176
1177
1178 static void
1179 watcher_remove_serial(MyType *myInstance)
1180 {
1181 if (myInstance->notifyNodes != NULL) {
1182 CFIndex i;
1183 CFIndex n = CFArrayGetCount(myInstance->notifyNodes);
1184
1185 for (i = 0; i < n; i++) {
1186 CFDataRef myData;
1187 MyNode *myNode;
1188
1189 myData = CFArrayGetValueAtIndex(myInstance->notifyNodes, i);
1190
1191 /* ALIGN: CF aligns to at least >8 bytes */
1192 myNode = (MyNode *)(void *)CFDataGetBytePtr(myData);
1193
1194 if (myNode->interface != myNode->interface_node) {
1195 IOObjectRelease(myNode->interface_node);
1196 }
1197 if (myNode->interface != MACH_PORT_NULL) {
1198 IOObjectRelease(myNode->interface);
1199 }
1200 IOObjectRelease(myNode->notification);
1201 }
1202
1203 CFRelease(myInstance->notifyNodes);
1204 myInstance->notifyNodes = NULL;
1205 }
1206
1207 if (myInstance->notifyIterator != MACH_PORT_NULL) {
1208 IOObjectRelease(myInstance->notifyIterator);
1209 myInstance->notifyIterator = MACH_PORT_NULL;
1210 }
1211
1212 if (myInstance->notifyPort != MACH_PORT_NULL) {
1213 IONotificationPortDestroy(myInstance->notifyPort);
1214 myInstance->notifyPort = NULL;
1215 }
1216
1217 return;
1218 }
1219
1220
1221 #pragma mark -
1222
1223
1224 static void
1225 watcher_add(MyType *myInstance)
1226 {
1227 CFBundleRef bundle;
1228
1229 bundle = CFBundleGetBundleWithIdentifier(CFSTR(MY_BUNDLE_ID));
1230 if (bundle != NULL) {
1231 CFStringRef action;
1232 CFBooleanRef bVal;
1233 CFDictionaryRef info;
1234
1235 info = CFBundleGetInfoDictionary(bundle);
1236
1237 bVal = CFDictionaryGetValue(info, CFSTR("Debug"));
1238 bVal = isA_CFBoolean(bVal);
1239 if (bVal != NULL) {
1240 myInstance->debug = CFBooleanGetValue(bVal);
1241 }
1242
1243 action = CFDictionaryGetValue(info, kSCNetworkInterfaceConfigurationActionKey);
1244 action = isA_CFString(action);
1245 if (action != NULL) {
1246 myInstance->configuration_action = action;
1247 } else {
1248 CFBooleanRef user_intervention;
1249
1250 user_intervention = CFDictionaryGetValue(info, CFSTR("User Intervention"));
1251 if (isA_CFBoolean(user_intervention) && !CFBooleanGetValue(user_intervention)) {
1252 myInstance->configuration_action = kSCNetworkInterfaceConfigurationActionValueConfigure;
1253 }
1254 }
1255 }
1256
1257 // initialize the list of known interfaces
1258 myInstance->interfaces_known = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
1259
1260 // add LAN interfaces
1261 watcher_add_lan(myInstance);
1262
1263 // add SERIAL interfaces
1264 watcher_add_serial(myInstance);
1265
1266 // auto-configure (as needed)
1267 updateInterfaceList(myInstance);
1268
1269 return;
1270 }
1271
1272
1273 static void
1274 watcher_remove(MyType *myInstance)
1275 {
1276 watcher_remove_lan(myInstance);
1277 watcher_remove_serial(myInstance);
1278
1279 if (myInstance->interfaces_known != NULL) {
1280 CFRelease(myInstance->interfaces_known);
1281 myInstance->interfaces_known = NULL;
1282 }
1283
1284 return;
1285 }
1286
1287
1288 #pragma mark -
1289 #pragma mark UserEventAgent stubs
1290
1291
1292 static HRESULT
1293 myQueryInterface(void *myInstance, REFIID iid, LPVOID *ppv)
1294 {
1295 CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(NULL, iid);
1296
1297 // Test the requested ID against the valid interfaces.
1298 if (CFEqual(interfaceID, kUserEventAgentInterfaceID)) {
1299 ((MyType *) myInstance)->_UserEventAgentInterface->AddRef(myInstance);
1300 *ppv = myInstance;
1301 CFRelease(interfaceID);
1302 return S_OK;
1303 }
1304
1305 if (CFEqual(interfaceID, IUnknownUUID)) {
1306 ((MyType *) myInstance)->_UserEventAgentInterface->AddRef(myInstance);
1307 *ppv = myInstance;
1308 CFRelease(interfaceID);
1309 return S_OK;
1310 }
1311
1312 // Requested interface unknown, bail with error.
1313 *ppv = NULL;
1314 CFRelease(interfaceID);
1315 return E_NOINTERFACE;
1316 }
1317
1318
1319 static ULONG
1320 myAddRef(void *myInstance)
1321 {
1322 ((MyType *) myInstance)->_refCount++;
1323 return ((MyType *) myInstance)->_refCount;
1324 }
1325
1326
1327 static ULONG
1328 myRelease(void *myInstance)
1329 {
1330 ((MyType *) myInstance)->_refCount--;
1331 if (((MyType *) myInstance)->_refCount == 0) {
1332 CFUUIDRef factoryID = ((MyType *) myInstance)->_factoryID;
1333
1334 if (factoryID != NULL) {
1335 CFPlugInRemoveInstanceForFactory(factoryID);
1336 CFRelease(factoryID);
1337
1338 watcher_remove((MyType *)myInstance);
1339 notify_remove((MyType *)myInstance, TRUE);
1340 freeAuthorization((MyType *)myInstance);
1341 }
1342 free(myInstance);
1343 return 0;
1344 }
1345
1346 return ((MyType *) myInstance)->_refCount;
1347 }
1348
1349
1350 static void
1351 myInstall(void *myInstance)
1352 {
1353 watcher_add((MyType *)myInstance);
1354 return;
1355 }
1356
1357
1358 static UserEventAgentInterfaceStruct UserEventAgentInterfaceFtbl = {
1359 NULL, // Required padding for COM
1360 myQueryInterface, // These three are the required COM functions
1361 myAddRef,
1362 myRelease,
1363 myInstall // Interface implementation
1364 };
1365
1366
1367 void *
1368 UserEventAgentFactory(CFAllocatorRef allocator, CFUUIDRef typeID)
1369 {
1370 #pragma unused(allocator)
1371 MyType *newOne = NULL;
1372
1373 if (CFEqual(typeID, kUserEventAgentTypeID)) {
1374 newOne = (MyType *)malloc(sizeof(MyType));
1375 memset(newOne, 0, sizeof(*newOne));
1376 newOne->_UserEventAgentInterface = &UserEventAgentInterfaceFtbl;
1377 newOne->_factoryID = (CFUUIDRef)CFRetain(kUserEventAgentFactoryID);
1378 CFPlugInAddInstanceForFactory(kUserEventAgentFactoryID);
1379 newOne->_refCount = 1;
1380 }
1381
1382 return newOne;
1383 }
1384
1385
1386 #ifdef MAIN
1387 int
1388 main(int argc, char **argv)
1389 {
1390 MyType *newOne = (MyType *)malloc(sizeof(MyType));
1391
1392 _sc_log = kSCLogDestinationFile;
1393 _sc_verbose = (argc > 1) ? TRUE : FALSE;
1394
1395 memset(newOne, 0, sizeof(*newOne));
1396 myInstall(newOne);
1397 CFRunLoopRun();
1398 exit(0);
1399 return (0);
1400 }
1401 #endif // MAIN