SCNetworkConfigurationPrivate.c   [plain text]


/*
 * Copyright (c) 2019, 2020 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

/*
 * Modification History
 *
 * October 4, 2018		Allan Nathanson <ajn@apple.com>
 * - initial revision
 */


#define	USE_SC_LOG_OR_PRINT	1		// use '_sc_log' to control os_log, printf

#include "SCNetworkConfigurationInternal.h"
#include "SCPreferencesInternal.h"
#include <IOKit/IOBSD.h>


static Boolean
savePreferences(SCPreferencesRef	prefs,
		CFStringRef		save_prefsID,
		CFStringRef		prefix,
		Boolean			remove,
		CFStringRef		extra_key,
		CFPropertyListRef	extra_value)
{
	const CFStringRef	keys[] = {
		kSCPrefCurrentSet,
		MODEL,
		kSCPrefNetworkServices,
		kSCPrefSets,
		kSCPrefSystem,
		kSCPrefVersion,
		kSCPrefVirtualNetworkInterfaces
	};
	Boolean			ok;
	SCPreferencesRef	save_prefs;

	// open [companion] backup
	save_prefs = SCPreferencesCreateCompanion(prefs, save_prefsID);

	for (CFIndex i = 0; i < (CFIndex)(sizeof(keys)/sizeof(keys[0])); i++) {
		CFStringRef	key	= keys[i];
		CFStringRef	src_key;
		CFTypeRef	val;

		src_key = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%@"), prefix, key);
		val = SCPreferencesGetValue(prefs, src_key);
		if (val != NULL) {
			SCPreferencesSetValue(save_prefs, key, val);
			if (remove) {
				SCPreferencesRemoveValue(prefs, src_key);
			}
		}
		CFRelease(src_key);
	}

	if (extra_key != NULL) {
		SCPreferencesSetValue(save_prefs, extra_key, extra_value);
	}

	ok = SCPreferencesCommitChanges(save_prefs);
	CFRelease(save_prefs);
	if (!ok) {
		SC_log(LOG_ERR, "could not save preferences (%@): %s",
		       save_prefsID,
		       SCErrorString(SCError()));
	}
	return ok;
}


__private_extern__
Boolean
__SCNetworkConfigurationBackup(SCPreferencesRef prefs)
{
	Boolean		ok;
	CFStringRef	save_prefsID;
	struct tm	tm_now;
	struct timeval	tv_now;

	SC_log(LOG_NOTICE, "creating [configuration] backup");

	(void)gettimeofday(&tv_now, NULL);
	(void)localtime_r(&tv_now.tv_sec, &tm_now);
	save_prefsID = CFStringCreateWithFormat(NULL,
						 NULL,
						 CFSTR("preferences-%4d-%02d-%02d-%02d%02d%02d.plist"),
						 tm_now.tm_year + 1900,
						 tm_now.tm_mon + 1,
						 tm_now.tm_mday,
						 tm_now.tm_hour,
						 tm_now.tm_min,
						 tm_now.tm_sec);
	ok = savePreferences(prefs, save_prefsID, CFSTR(""), FALSE, NULL, NULL);
	CFRelease(save_prefsID);
	return ok;
}


Boolean
__SCNetworkConfigurationSaveModel(SCPreferencesRef prefs, CFStringRef model)
{
	Boolean		ok;
	CFStringRef	save_prefsID;

	SC_log(LOG_NOTICE, "creating [per-device] backup: %@", model);

	save_prefsID = CFStringCreateWithFormat(NULL, NULL, CFSTR("preferences-%@.plist"), model);
	ok = savePreferences(prefs, save_prefsID, CFSTR(""), TRUE, MODEL, model);
	CFRelease(save_prefsID);
	return ok;
}


static Boolean
needsUpdate(SCPreferencesRef prefs, int new_version)
{
	CFNumberRef	num;
	int		old_version	= 0;

	if (prefs == NULL) {
		// if no prefs, no updated needed
		return FALSE;
	}

	num = SCPreferencesGetValue(prefs, kSCPrefVersion);
	if (!isA_CFNumber(num) ||
	    !CFNumberGetValue(num, kCFNumberIntType, &old_version)) {
		old_version = 0;
	}
	if (old_version == new_version) {
		// if no update is needed
		return FALSE;
	}

	return TRUE;
}


static Boolean
lockWithSync(SCPreferencesRef prefs)
{
	Boolean		ok;

	assert(prefs != NULL);
	ok = SCPreferencesLock(prefs, TRUE);
	if (!ok && (SCError() == kSCStatusStale)) {
		SCPreferencesSynchronize(prefs);
		ok = SCPreferencesLock(prefs, TRUE);
	}

	return ok;
}


Boolean
__SCNetworkConfigurationUpgrade(SCPreferencesRef	*prefs_p,
				SCPreferencesRef	*ni_prefs_p,
				Boolean			commit)
{
	SCPreferencesRef	ni_prefs	= NULL;
	Boolean			ni_prefs_added	= FALSE;
	const int		new_version	= NETWORK_CONFIGURATION_VERSION;
	CFNumberRef		num;
	Boolean			ok		= FALSE;
	SCPreferencesRef	prefs		= NULL;
	Boolean			prefs_added	= FALSE;

	//
	// The following table describes how the SPI is called (input parameters), what actions
	// are performed, and whether any changes should be committed as part of the call.
	//
	// +====================+===========================+===========================+========+
	// |                    |     preferences.plist     |  NetworkInterfaces.plist  |        |
	// |                    +=============+=============+=============+=============+ COMMIT |
	// |                    |     ptr     |    plist    |     ptr     |    plist    |        |
	// +====================+=============+=============+=============+=============+========+
	// | InterfaceNamer     |    NULL     | CREATE, REL |  &ni_prefs  |     USE     |  YES   |
	// +====================+=============+=============+=============+=============+========+
	// | PreferencesMonitor |   &prefs    |     USE     |    NULL     |  NO UPDATE  |  YES   |
	// +====================+=============+=============+=============+=============+========+
	// | scutil             |   &prefs    |     USE     |  &ni_prefs  | USE/CREATE  |   NO   |
	// +====================+=============+=============+=============+=============+========+
	//
	// For InterfaceNamer, we are passed a reference to the SCPreferences[Ref] for the
	// NetworkInterfaces.plist.  During the upgrade process we create (and then release)
	// the companion preferences.plist.  Any needed changes will be committed to both
	// plists.
	//
	// For PreferencesMonitor, we are passed a reference to the SCPreferences[Ref] for
	// the preferences.plist.  Any needed changes to the plist will be committed.  The
	// companion NetworkInterfaces.plist is not passed nor is it referenced/modified.
	//
	// For scutil, we are passed references to the SCPreferences[Ref] for both the
	// preferences.plist and NetworkInterfaces.plist.  The NetworkInterfaces.plist
	// reference will be used, if already open.  Else, the companion will be created
	// and returned.  Regardless, any changes made will not be committed (we expect
	// one to use scutil's "commit" command).
	//

	if (prefs_p != NULL) {
		prefs = *prefs_p;
	}

	if (ni_prefs_p != NULL) {
		ni_prefs = *ni_prefs_p;
	}

	if ((prefs_p == NULL) && (ni_prefs_p != NULL) && (ni_prefs != NULL)) {
		// Here, we have been called by InterfaceNamer and need to get the [companion]
		// preferences.plist (we need to update both)
		prefs = SCPreferencesCreateCompanion(ni_prefs, NULL);
		if (prefs == NULL) {
			SC_log(LOG_ERR,
			       "__SCNetworkConfigurationUpgrade(): could not open [preferences.plist]: %s",
			       SCErrorString(SCError()));
			return FALSE;
		}
		prefs_added = TRUE;
	}

	if ((prefs_p != NULL) && (prefs != NULL) && (ni_prefs_p != NULL) && (ni_prefs == NULL)) {
		// Here, we have been called by scutil with the [companion] NetworkInterfaces.plist
		// not yet open.  Open the companion so that we can update both.
		ni_prefs = SCPreferencesCreateCompanion(prefs, INTERFACES_DEFAULT_CONFIG);
		if (ni_prefs == NULL) {
			SC_log(LOG_ERR,
			       "__SCNetworkConfigurationUpgrade(): could not open [NetworkInterfaces.plist]: %s",
			       SCErrorString(SCError()));
			return FALSE;
		}
		ni_prefs_added = TRUE;
	}

	if (!needsUpdate(prefs,    NETWORK_CONFIGURATION_VERSION) &&
	    !needsUpdate(ni_prefs, NETWORK_CONFIGURATION_VERSION)) {
		goto done;
	}

	// lock [preferences.plist] changes while we are updating
	ok = lockWithSync(prefs);
	if (!ok) {
		SC_log(LOG_ERR,
		       "__SCNetworkConfigurationUpgrade(): could not lock [preferences.plist]: %s",
		       SCErrorString(SCError()));
		goto done;
	}

	if (ni_prefs != NULL) {
		// lock [NetworkInterfaces.plist] changes while we are updating
		ok = lockWithSync(ni_prefs);
		if (!ok) {
			SC_log(LOG_ERR,
			       "__SCNetworkConfigurationUpgrade(): could not lock [NetworkInterfaces.plist]: %s",
			       SCErrorString(SCError()));
			SCPreferencesUnlock(prefs);
			goto done;
		}
	}

	// first, cleanup any leftover cruft from the configuration
	__SCNetworkConfigurationClean(prefs, ni_prefs);

	// update the version(s)
	num = CFNumberCreate(NULL, kCFNumberIntType, &new_version);
	SCPreferencesSetValue(prefs, kSCPrefVersion, num);
	CFRelease(num);
	if (ni_prefs != NULL) {
		num = CFNumberCreate(NULL, kCFNumberIntType, &new_version);
		SCPreferencesSetValue(ni_prefs, kSCPrefVersion, num);
		CFRelease(num);
	}

	if (commit) {
		// commit the [preferences.plist] changes
		ok = SCPreferencesCommitChanges(prefs);
		if (!ok) {
			SC_log(LOG_ERR,
			       "__SCNetworkConfigurationUpgrade(): update not saved [preferences.plist]: %s",
			       SCErrorString(SCError()));
		}
		if (ok) {
			ok = SCPreferencesApplyChanges(prefs);
			if (!ok) {
				SC_log(LOG_ERR,
				       "__SCNetworkConfigurationUpgrade(): update not applied [preferences.plist]: %s",
				       SCErrorString(SCError()));
			}
		}
	}
	SCPreferencesUnlock(prefs);

	if (ni_prefs != NULL) {
		if (commit) {
			// commit the [NetworkInterfaces.plist] changes
			if (ok) {
				ok = SCPreferencesCommitChanges(ni_prefs);
				if (!ok) {
					SC_log(LOG_ERR,
					       "__SCNetworkConfigurationUpgrade(): update not saved [NetworkInterfaces.plist]: %s",
					       SCErrorString(SCError()));
				}
			}
		}
		SCPreferencesUnlock(ni_prefs);
	}

    done :

	if (prefs_added) {
//		if (ok && (prefs_p != NULL)) {
//			*prefs_p = CFRetain(prefs);
//		}
		CFRelease(prefs);
	}

	if (ni_prefs_added) {
		if (ok && (ni_prefs_p != NULL)) {
			*ni_prefs_p = CFRetain(ni_prefs);
		}
		CFRelease(ni_prefs);
	}

	return ok;
}


#pragma mark -
#pragma mark Remove "Hidden" Interface Configurations


static Boolean
isThin(CFArrayRef interfaces, CFStringRef bsdName)
{
	Boolean		thin;

	thin = CFArrayContainsValue(interfaces,
				    CFRangeMake(0, CFArrayGetCount(interfaces)),
				    bsdName);
	return thin;
}


static Boolean
thinAdd(CFMutableArrayRef interfaces, CFStringRef bsdName)
{
	if (!CFArrayContainsValue(interfaces,
				  CFRangeMake(0, CFArrayGetCount(interfaces)),
				  bsdName)) {
		CFArrayAppendValue(interfaces, bsdName);
		return TRUE;
	}

	return FALSE;
}


static Boolean
thinRemove(CFMutableArrayRef interfaces, CFStringRef bsdName)
{
	CFIndex		n;

	n = CFArrayGetFirstIndexOfValue(interfaces,
					CFRangeMake(0, CFArrayGetCount(interfaces)),
					bsdName);
	if (n != kCFNotFound) {
		CFArrayRemoveValueAtIndex(interfaces, n);
		return TRUE;
	}

	return FALSE;
}


static CF_RETURNS_RETAINED CFStringRef
serviceMatchesTemplate(SCPreferencesRef prefs, SCNetworkServiceRef existingService)
{
	CFStringRef		conflict	= NULL;
	SCNetworkInterfaceRef	existingInterface;
	CFIndex			n;
	CFArrayRef		protocols;
	CFMutableArrayRef	protocolTypes;
	SCNetworkServiceRef	templateService;

	// create a temporary network service (so that we can get the template configuration)
	existingInterface = SCNetworkServiceGetInterface(existingService);
	if (existingInterface == NULL) {
		conflict = CFStringCreateCopy(NULL, CFSTR("could not get interface for service"));
		return conflict;
	}

	templateService = SCNetworkServiceCreate(prefs, existingInterface);
	if (templateService == NULL) {
		conflict = CFStringCreateCopy(NULL, CFSTR("could not create service for interface"));
		return conflict;
	}

	(void) SCNetworkServiceEstablishDefaultConfiguration(templateService);

	protocolTypes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);

	// get protocol types from the existing service
	protocols = SCNetworkServiceCopyProtocols(existingService);
	if (protocols != NULL) {
		n = CFArrayGetCount(protocols);

		for (CFIndex i = 0; i < n; i++) {
			SCNetworkProtocolRef	protocol;
			CFStringRef		protocolType;

			protocol = CFArrayGetValueAtIndex(protocols, i);
			protocolType = SCNetworkProtocolGetProtocolType(protocol);
			if (!CFArrayContainsValue(protocolTypes,
						  CFRangeMake(0, CFArrayGetCount(protocolTypes)),
						  protocolType)) {
				CFArrayAppendValue(protocolTypes, protocolType);
			}
		}

		CFRelease(protocols);
	}

	// get protocol types from the template service
	protocols = SCNetworkServiceCopyProtocols(templateService);
	if (protocols != NULL) {
		n = CFArrayGetCount(protocols);

		for (CFIndex i = 0; i < n; i++) {
			SCNetworkProtocolRef	protocol;
			CFStringRef		protocolType;

			protocol = CFArrayGetValueAtIndex(protocols, i);
			protocolType = SCNetworkProtocolGetProtocolType(protocol);
			if (!CFArrayContainsValue(protocolTypes,
						  CFRangeMake(0, CFArrayGetCount(protocolTypes)),
						  protocolType)) {
				CFArrayAppendValue(protocolTypes, protocolType);
			}
		}

		CFRelease(protocols);
	}

	// compare the existing protocols with the template
	n = CFArrayGetCount(protocolTypes);
	for (CFIndex i = 0; i < n; i++) {
		CFDictionaryRef		existingConfiguration	= NULL;
		SCNetworkProtocolRef	existingProtocol;
		Boolean			match;
		CFStringRef		protocolType;
		CFDictionaryRef		templateConfiguration	= NULL;
		SCNetworkProtocolRef	templateProtocol;

		protocolType = CFArrayGetValueAtIndex(protocolTypes, i);
		existingProtocol = SCNetworkServiceCopyProtocol(existingService, protocolType);
		templateProtocol = SCNetworkServiceCopyProtocol(templateService, protocolType);

		do {
			// compare "enabled"
			match = ((existingProtocol != NULL) &&
				 (templateProtocol != NULL) &&
				 (SCNetworkProtocolGetEnabled(existingProtocol) == SCNetworkProtocolGetEnabled(templateProtocol)));
			if (!match) {
				conflict = CFStringCreateWithFormat(NULL, NULL,
								    CFSTR("conflicting %@ enable/disable"),
								    protocolType);
				break;		// if enable/disable conflict
			}

			if (existingProtocol != NULL) {
				existingConfiguration = SCNetworkProtocolGetConfiguration(existingProtocol);
			}
			if (templateProtocol != NULL) {
				templateConfiguration = SCNetworkProtocolGetConfiguration(templateProtocol);
			}
			match = _SC_CFEqual(existingConfiguration, templateConfiguration);
			if (!match) {
				conflict = CFStringCreateWithFormat(NULL, NULL,
								    CFSTR("conflicting %@ configuration"),
								    protocolType);
				break;		// if configuration conflict
			}
		} while (FALSE);

		if (existingProtocol != NULL) CFRelease(existingProtocol);
		if (templateProtocol != NULL) CFRelease(templateProtocol);
		if (!match) {
			break;
		}
	}

	(void) SCNetworkServiceRemove(templateService);
	CFRelease(templateService);
	CFRelease(protocolTypes);
	return conflict;
}


static Boolean
effectivelyHiddenConfiguration(SCNetworkInterfaceRef interface)
{
	const CFStringRef	known[] = {
		CFSTR("Apple TV"),
		CFSTR("Watch"),
		CFSTR("iPad"),
		CFSTR("iPhone"),
		CFSTR("iPod"),
	};
	CFStringRef		name;

	name = SCNetworkInterfaceGetLocalizedDisplayName(interface);
	for (int i = 0; i < (int)(sizeof(known) / sizeof(known[0])); i++) {
		if (CFStringHasPrefix(name, known[i])) {
			return TRUE;
		}
	}

	return FALSE;
}


static Boolean
__SCNetworkConfigurationCleanHiddenInterfaces(SCPreferencesRef prefs, SCPreferencesRef ni_prefs)
{
#pragma unused(prefs)
#pragma unused(ni_prefs)
	CFStringRef		bsdName;
	Boolean			changed		= FALSE;
	CFArrayRef		interfaces;
	CFMutableArrayRef	interfaces_thin;
	CFIndex			n;
	CFDictionaryRef		nat_config;
	SCPreferencesRef	nat_prefs;
	CFArrayRef		services;
	int			updated		= 0;

	// build a list of interfaces we "could" remove

	interfaces_thin = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);

	services = SCNetworkServiceCopyAll(prefs);
	if (services != NULL) {
		n = CFArrayGetCount(services);
		for (CFIndex i = 0; i < n; i++) {
			CFStringRef		conflict;
			SCNetworkInterfaceRef	interface;
			SCNetworkServiceRef	service;
			const char		*thin	= NULL;

			service = CFArrayGetValueAtIndex(services, i);
			interface = SCNetworkServiceGetInterface(service);
			bsdName = SCNetworkInterfaceGetBSDName(interface);

			if (bsdName == NULL) {
				// if no interface name
				if ((_sc_log == 1) || _sc_debug) {
					SC_log(LOG_INFO,
					       "skipping service : %@ : %@ (no interface)",
					       SCNetworkServiceGetServiceID(service),
					       SCNetworkServiceGetName(service));
				}
				continue;
			}

			if (_SCNetworkInterfaceIsHiddenConfiguration(interface)) {
				thin = "hidden";
			} else if (effectivelyHiddenConfiguration(interface)) {
				thin = "effectively hidden";
			} else {
				// if not HiddenConfiguration
				if ((_sc_log == 1) || _sc_debug) {
					SC_log(LOG_INFO,
					       "skipping service : %@ : %@ : %@ (not hidden)",
						SCNetworkServiceGetServiceID(service),
						SCNetworkServiceGetName(service),
						bsdName);
				}
				continue;
			}

			conflict = serviceMatchesTemplate(prefs, service);
			if (conflict != NULL) {
				// if any part of the service's configuration was changed
				if ((_sc_log == 1) || _sc_debug) {
					SC_log(LOG_INFO,
					       "skipping service : %@ : %@ : %@ (%s, non-default, %@)",
					       SCNetworkServiceGetServiceID(service),
					       SCNetworkServiceGetName(service),
					       bsdName,
					       thin,
					       conflict);
				}
				CFRelease(conflict);
				continue;
			}

			if ((_sc_log == 1) || _sc_debug) {
				SC_log(LOG_INFO, "candidate interface : %@ (%s)", bsdName, thin);
			}

			thinAdd(interfaces_thin, bsdName);
		}
	}

	// remove any virtual interfaces from the list

#if	!TARGET_OS_IPHONE
	interfaces = SCBondInterfaceCopyAll(prefs);
	if (interfaces != NULL) {
		CFIndex		n;

		n = CFArrayGetCount(interfaces);
		for (CFIndex i = 0; i < n; i++) {
			SCBondInterfaceRef	bondInterface;
			CFArrayRef		members;
			CFIndex			nn;

			bondInterface = CFArrayGetValueAtIndex(interfaces, i);
			members = SCBondInterfaceGetMemberInterfaces(bondInterface);
			nn = (members != NULL) ? CFArrayGetCount(members) : 0;
			for (CFIndex ii = 0; ii < nn; ii++) {
				SCNetworkInterfaceRef	member;

				member = CFArrayGetValueAtIndex(members, ii);
				bsdName = SCNetworkInterfaceGetBSDName(member);
				if ((bsdName != NULL) &&
				    thinRemove(interfaces_thin, bsdName)) {
					if ((_sc_log == 1) || _sc_debug) {
						SC_log(LOG_INFO, "skipping interface : %@ (bond member)", bsdName);
					}
				}
			}
		}

		CFRelease(interfaces);
	}
#endif	// !TARGET_OS_IPHONE

	interfaces = SCBridgeInterfaceCopyAll(prefs);
	if (interfaces != NULL) {
		CFIndex		n;

		n = CFArrayGetCount(interfaces);
		for (CFIndex i = 0; i < n; i++) {
			SCBridgeInterfaceRef	bridgeInterface;
			CFArrayRef		members;
			CFIndex			nn;

			bridgeInterface = CFArrayGetValueAtIndex(interfaces, i);
			members = SCBridgeInterfaceGetMemberInterfaces(bridgeInterface);
			nn = (members != NULL) ? CFArrayGetCount(members) : 0;
			for (CFIndex ii = 0; ii < nn; ii++) {
				SCNetworkInterfaceRef	member;

				member = CFArrayGetValueAtIndex(members, ii);
				bsdName = SCNetworkInterfaceGetBSDName(member);
				if ((bsdName != NULL) &&
				    thinRemove(interfaces_thin, bsdName)) {
					if ((_sc_log == 1) || _sc_debug) {
						SC_log(LOG_INFO, "skipping interface : %@ (bridge member)", bsdName);
					}
				}
			}
		}

		CFRelease(interfaces);
	}

	interfaces = SCVLANInterfaceCopyAll(prefs);
	if (interfaces != NULL) {
		CFIndex		n;

		n = CFArrayGetCount(interfaces);
		for (CFIndex i = 0; i < n; i++) {
			SCBridgeInterfaceRef	vlanInterface;
			SCNetworkInterfaceRef	physicalInterface;

			vlanInterface = CFArrayGetValueAtIndex(interfaces, i);
			physicalInterface = SCVLANInterfaceGetPhysicalInterface(vlanInterface);
			bsdName = SCNetworkInterfaceGetBSDName(physicalInterface);
			if ((bsdName != NULL) &&
			    thinRemove(interfaces_thin, bsdName)) {
				if ((_sc_log == 1) || _sc_debug) {
					SC_log(LOG_INFO, "skipping interface : %@ (vlan physical)", bsdName);
				}
			}
		}

		CFRelease(interfaces);
	}

	// remove any "shared" interfaces from the list

	nat_prefs = SCPreferencesCreateCompanion(prefs, CFSTR("com.apple.nat.plist"));
	nat_config = SCPreferencesGetValue(nat_prefs, CFSTR("NAT"));
	if (isA_CFDictionary(nat_config)) {
		CFStringRef	sharedFrom	= NULL;
		CFArrayRef	sharedTo	= NULL;

		if (CFDictionaryGetValueIfPresent(nat_config,
						  CFSTR("PrimaryService"),
						  (const void **)&sharedFrom) &&
			isA_CFString(sharedFrom)) {
			SCNetworkInterfaceRef	interface;
			SCNetworkServiceRef	service;

			// if "Share your connection from" service configured
			service = SCNetworkServiceCopy(prefs, sharedFrom);
			if (service != NULL) {
				interface = SCNetworkServiceGetInterface(service);
				bsdName = SCNetworkInterfaceGetBSDName(interface);
				if ((bsdName != NULL) &&
				    thinRemove(interfaces_thin, bsdName)) {
					if ((_sc_log == 1) || _sc_debug) {
						SC_log(LOG_INFO, "skipping interface : %@ (Share your connection from)", bsdName);
					}
				}
				CFRelease(service);
			} else {
				SC_log(LOG_INFO, "keeping [not found] service : %@ (Share your connection from)", sharedFrom);
			}
		}

	   if (CFDictionaryGetValueIfPresent(nat_config,
					     CFSTR("SharingDevices"),
					     (const void **)&sharedTo) &&
		   isA_CFArray(sharedTo)) {
		   // if "To computers using" interfaces configured
		   n = CFArrayGetCount(sharedTo);
		   for (CFIndex i = 0; i < n; i++) {
			   bsdName = CFArrayGetValueAtIndex(sharedTo, i);
			   if (thinRemove(interfaces_thin, bsdName)) {
				   if ((_sc_log == 1) || _sc_debug) {
					   SC_log(LOG_INFO, "skipping interface : %@ (To computers using)", bsdName);
				   }
			   }
		   }
	   }
	}
	CFRelease(nat_prefs);

	// thin preferences.plist
	n = (services != NULL) ? CFArrayGetCount(services) : 0;
	if (n > 0) {
		updated = 0;

		for (CFIndex i = 0; i < n; i++) {
			SCNetworkInterfaceRef	interface;
			SCNetworkServiceRef	service;

			service = CFArrayGetValueAtIndex(services, i);
			interface = SCNetworkServiceGetInterface(service);
			bsdName = SCNetworkInterfaceGetBSDName(interface);
			if (bsdName == NULL) {
				// if no interface name
				continue;
			}

			if (!isThin(interfaces_thin, bsdName)) {
				// if not thinned
				continue;
			}

			// remove this service associated with a "thinned" interface
			if ((_sc_log == 1) || _sc_verbose) {
				SC_log(LOG_INFO,
				       "thinned network service : %@ : %@ : %@",
				       SCNetworkServiceGetServiceID(service),
				       SCNetworkServiceGetName(service),
				       bsdName);
			}
			SCNetworkServiceRemove(service);
			updated++;
		}

		if (updated > 0) {
			if ((_sc_log == 1) || _sc_debug) {
				SC_log(LOG_NOTICE,
				       "Updating \"preferences.plist\" (thinned %d service%s)",
				       updated,
				       (updated != 1) ? "s" : "");
			}
			changed = TRUE;
		}
	}

	// thin NetworkInterfaces.plist
	interfaces = SCPreferencesGetValue(ni_prefs, INTERFACES);
	interfaces = isA_CFArray(interfaces);
	n = (interfaces != NULL) ? CFArrayGetCount(interfaces) : 0;
	if (n > 0) {
		CFMutableArrayRef	interfaces_new;

		updated = 0;

		interfaces_new = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
		n = CFArrayGetCount(interfaces);
		for (CFIndex i = 0; i < n; i++) {
			CFDictionaryRef	if_dict;

			if_dict = CFArrayGetValueAtIndex(interfaces, i);
			bsdName = CFDictionaryGetValue(if_dict, CFSTR(kIOBSDNameKey));
			if (isThin(interfaces_thin, bsdName)) {
				// remove this "thinned" interface
				if ((_sc_log == 1) || _sc_verbose) {
					SC_log(LOG_INFO, "thinned network interface : %@", bsdName);
				}
				updated++;
				continue;
			}

			CFArrayAppendValue(interfaces_new, if_dict);
		}
		SCPreferencesSetValue(ni_prefs, INTERFACES, interfaces_new);
		CFRelease(interfaces_new);

		if (updated > 0) {
			if ((_sc_log == 1) || _sc_debug) {
				SC_log(LOG_INFO,
				       "Updating \"NetworkInterfaces.plist\" (thinned %d interface%s)",
				       updated,
				       (updated != 1) ? "s" : "");
			}
			changed = TRUE;
		}
	}

	if (services != NULL) CFRelease(services);
	CFRelease(interfaces_thin);
	return changed;
}


#pragma mark -
#pragma mark Remove [SCNetworkMigration] Inline Backups


static void
thinInlineBackup(const void *value, void *context)
{
	CFStringRef		backup		= (CFStringRef)value;
	char			*backup_str;
	SCPreferencesRef	prefs		= (SCPreferencesRef)context;
	CFStringRef		save_prefix;
	CFStringRef		save_prefsID	= NULL;

	SC_log(LOG_NOTICE, "thinning [inline] backup: %@", backup);

	save_prefix = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ : "), backup);
	backup_str = _SC_cfstring_to_cstring(backup, NULL, 0, kCFStringEncodingASCII);
	if (backup_str != NULL) {
		struct tm	save_tm	= { 0 };

		if (strptime(backup_str, "%Y-%m-%d %H:%M:%S", &save_tm) != NULL) {
			save_prefsID = CFStringCreateWithFormat(NULL,
								NULL,
								CFSTR("preferences-%4d-%02d-%02d-%02d%02d%02d.plist"),
								save_tm.tm_year + 1900,
								save_tm.tm_mon + 1,
								save_tm.tm_mday,
								save_tm.tm_hour,
								save_tm.tm_min,
								save_tm.tm_sec);
		}
		CFAllocatorDeallocate(NULL, backup_str);
	}
	if (save_prefsID == NULL) {
		save_prefsID = CFStringCreateWithFormat(NULL, NULL, CFSTR("preferences-%@.plist"), backup);
	}
	savePreferences(prefs, save_prefsID, save_prefix, TRUE, NULL, NULL);
	CFRelease(save_prefsID);
	CFRelease(save_prefix);
	return;
}


static Boolean
__SCNetworkConfigurationCleanInlineBackups(SCPreferencesRef prefs)
{
	CFMutableSetRef	backups	= NULL;
	Boolean		cleaned	= FALSE;
	CFArrayRef	keys;
	CFIndex		n;
	CFStringRef	suffix;

	keys = SCPreferencesCopyKeyList(prefs);
	if (keys == NULL) {
		return FALSE;
	}

	suffix = CFStringCreateWithFormat(NULL, NULL, CFSTR(" : %@"), kSCPrefSets);
	n = CFArrayGetCount(keys);
	for (CFIndex i = 0; i < n; i++) {
		CFStringRef		key	= CFArrayGetValueAtIndex(keys, i);
		CFMutableStringRef	str;

		if (CFStringHasSuffix(key, suffix)) {
			// if "<backup-date> : Sets"
			if (backups == NULL) {
				backups = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
			}
			str = CFStringCreateMutableCopy(NULL, 0, key);
			CFStringTrim(str, suffix);
			CFSetAddValue(backups, str);
			CFRelease(str);
			continue;
		}
	}
	CFRelease(suffix);
	CFRelease(keys);

	if (backups != NULL) {
		CFSetApplyFunction(backups, thinInlineBackup, (void *)prefs);
		CFRelease(backups);
		cleaned = TRUE;
	}

	return cleaned;
}


#pragma mark -
#pragma mark Remove [new device type] Inline Backups


static void
thinInlineModel(const void *value, void *context)
{
	CFStringRef		model	= (CFStringRef)value;
	SCPreferencesRef	prefs	= (SCPreferencesRef)context;
	CFStringRef		save_prefix;
	CFStringRef		save_prefsID;

	SC_log(LOG_NOTICE, "thinning [per-model] backup: %@", model);

	save_prefix = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@:"), model);
	save_prefsID = CFStringCreateWithFormat(NULL, NULL, CFSTR("preferences-%@.plist"), model);
	savePreferences(prefs, save_prefsID, save_prefix, TRUE, MODEL, model);
	CFRelease(save_prefsID);
	CFRelease(save_prefix);
	return;
}


static Boolean
__SCNetworkConfigurationCleanInlineModels(SCPreferencesRef prefs)
{
	Boolean		cleaned	= FALSE;
	CFArrayRef	keys;
	CFMutableSetRef	models	= NULL;
	CFIndex		n;
	CFStringRef	suffix;

	keys = SCPreferencesCopyKeyList(prefs);
	if (keys == NULL) {
		return FALSE;
	}

	suffix = CFStringCreateWithFormat(NULL, NULL, CFSTR(":%@"), kSCPrefSets);
	n = CFArrayGetCount(keys);
	for (CFIndex i = 0; i < n; i++) {
		CFStringRef		key	= CFArrayGetValueAtIndex(keys, i);
		CFMutableStringRef	str;

		if (CFStringHasSuffix(key, suffix)) {
			// if "<backup-date> : Sets"
			if (models == NULL) {
				models = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
			}
			str = CFStringCreateMutableCopy(NULL, 0, key);
			CFStringTrim(str, suffix);
			CFSetAddValue(models, str);
			CFRelease(str);
			continue;
		}
	}
	CFRelease(suffix);
	CFRelease(keys);

	if (models != NULL) {
		CFSetApplyFunction(models, thinInlineModel, (void *)prefs);
		CFRelease(models);
		cleaned = TRUE;
	}

	return cleaned;
}


#pragma mark -
#pragma mark Remove Orphaned Services


/*
static Boolean
__SCNetworkConfigurationCleanOrphanedServices(SCPreferencesRef prefs)
{
#pragma unused(prefs)
	return FALSE;
}
*/


#pragma mark -
#pragma mark Cleanup network service order issues


static Boolean
__SCNetworkConfigurationCleanServiceOrderIssues(SCPreferencesRef prefs)
{
#pragma unused(prefs)
	Boolean		cleaned	= FALSE;
	CFIndex		nSets;
	CFArrayRef	sets;

	sets = SCNetworkSetCopyAll(prefs);
	nSets = (sets != NULL) ? CFArrayGetCount(sets) : 0;
	for (CFIndex iSets = 0; iSets < nSets; iSets++) {
		CFIndex			iServices;
		CFMutableSetRef		known		= NULL;
		CFIndex			nServices;
		SCNetworkSetRef		set		= CFArrayGetValueAtIndex(sets, iSets);
		CFStringRef		setID		= SCNetworkSetGetSetID(set);
		CFArrayRef		order		= SCNetworkSetGetServiceOrder(set);
		CFMutableArrayRef	newOrder	= NULL;

		iServices = 0;
		nServices = (order != NULL) ? CFArrayGetCount(order) : 0;
		if (nServices > 0) {
			known = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
			newOrder = CFArrayCreateMutableCopy(NULL, 0, order);
		}

		while (iServices < nServices) {
			SCNetworkServiceRef	service;
			CFStringRef		serviceID	= CFArrayGetValueAtIndex(newOrder, iServices);

			// check if serviceID already known/processed
			if (CFSetContainsValue(known, serviceID)) {
				// if duplicate/removed service, remove from serviceOrder
				if ((_sc_log == 1) || _sc_debug) {
					SC_log(LOG_NOTICE,
					       "set: %@, removing serviceID %@ (duplicate/removed)",
					       setID,
					       serviceID);
				}
				CFArrayRemoveValueAtIndex(newOrder, iServices);
				nServices--;
				cleaned = TRUE;
				continue;
			}

			// track this serviceID as known, already removed, or removed below
			CFSetAddValue(known, serviceID);

			// validate serviceID
			service = SCNetworkServiceCopy(prefs, serviceID);
			if (service == NULL) {
				// if no service, remove from serviceOrder
				if ((_sc_log == 1) || _sc_debug) {
					SC_log(LOG_NOTICE,
					       "set: %@, removing serviceID %@ (no service)",
					       setID,
					       serviceID);
				}
				CFArrayRemoveValueAtIndex(newOrder, iServices);
				nServices--;
				cleaned = TRUE;
				continue;
			}

			if (!__SCNetworkServiceExists(service)) {
				// if service already removed, remove from serviceOrder
				if ((_sc_log == 1) || _sc_debug) {
					SC_log(LOG_NOTICE,
					       "set: %@, removing serviceID %@ (service already removed)",
					       setID,
					       serviceID);
				}
				CFArrayRemoveValueAtIndex(newOrder, iServices);
				nServices--;
				cleaned = TRUE;
				CFRelease(service);
				continue;
			}

			CFRelease(service);
			iServices++;
		}

		if (known != NULL) {
			CFRelease(known);
		}

		if (newOrder != NULL) {
			if (cleaned) {
				SCNetworkSetSetServiceOrder(set, newOrder);
			}
			CFRelease(newOrder);
		}
	}

	if (sets != NULL) {
		CFRelease(sets);
	}

	return cleaned;
}


#pragma mark -
#pragma mark Cleanup Network Configuration(s)


Boolean
__SCNetworkConfigurationClean(SCPreferencesRef prefs, SCPreferencesRef ni_prefs)
{
	Boolean		changed;
	Boolean		updated	=	FALSE;

	changed = __SCNetworkConfigurationCleanInlineBackups(prefs);
	if (changed) {
		SC_log(LOG_NOTICE, "network configuration: unwanted inline backups removed");
		updated = TRUE;
	}

	changed = __SCNetworkConfigurationCleanInlineModels(prefs);
	if (changed) {
		SC_log(LOG_NOTICE, "network configuration: unwanted device backups removed");
		updated = TRUE;
	}

	if (ni_prefs != NULL) {
		changed = __SCNetworkConfigurationCleanHiddenInterfaces(prefs, ni_prefs);
		if (changed) {
			SC_log(LOG_NOTICE, "network configuration: hidden interface configurations removed");
			updated = TRUE;
		}
	}

	changed = __SCNetworkConfigurationCleanServiceOrderIssues(prefs);
	if (changed) {
		SC_log(LOG_NOTICE, "network configuration: ServiceOrder cleaned");
		updated = TRUE;
	}

	return updated;
}