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