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