/*
 * Copyright (c) 2004 Apple Computer, 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
 *
 * May 27, 2004		Allan Nathanson <ajn@apple.com>
 * - initial revision
 */


#include <CoreFoundation/CoreFoundation.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCValidation.h>
#include <SystemConfiguration/SCPrivate.h>

#include <sys/ioctl.h>
#include <net/if.h>


__private_extern__ CFDictionaryRef
__getPrefsConfiguration(SCPreferencesRef prefs, CFStringRef path)
{
	CFDictionaryRef config;
	CFIndex		n;

	config = SCPreferencesPathGetValue(prefs, path);

	n = isA_CFDictionary(config) ? CFDictionaryGetCount(config) : 0;
	switch (n) {
		case 0 :
			// ignore empty configuration entities
			config = NULL;
			break;
		case 1 :
			if (CFDictionaryContainsKey(config, kSCResvInactive)) {
				// ignore [effectively] empty configuration entities
				config = NULL;
			}
			break;
		default :
			break;
	}

	return config;
}


__private_extern__ Boolean
__setPrefsConfiguration(SCPreferencesRef	prefs,
			CFStringRef		path,
			CFDictionaryRef		config,
			Boolean			keepInactive)
{
	CFMutableDictionaryRef  newConfig       = NULL;
	Boolean			ok		= FALSE;

	if (config != NULL) {
		if (!isA_CFDictionary(config)) {
			_SCErrorSet(kSCStatusInvalidArgument);
			return FALSE;
		}
		newConfig = CFDictionaryCreateMutableCopy(NULL, 0, config);
	} else {
		newConfig = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	}

	if (keepInactive) {
		CFDictionaryRef	curConfig;

		/*
		 * preserve enabled/disabled state
		 */

		curConfig = SCPreferencesPathGetValue(prefs, path);
		if (isA_CFDictionary(curConfig) && CFDictionaryContainsKey(curConfig, kSCResvInactive)) {
			// if currently disabled
			CFDictionarySetValue(newConfig, kSCResvInactive, kCFBooleanTrue);
		} else {
			// if currently enabled
			CFDictionaryRemoveValue(newConfig, kSCResvInactive);
		}
	}

	/*
	 * set new configuration
	 */

	if (CFDictionaryGetCount(newConfig) == 0) {
		CFRelease(newConfig);
		newConfig = NULL;
	}

	if (newConfig == NULL) {
		ok = SCPreferencesPathRemoveValue(prefs, path);
	} else {
		ok = SCPreferencesPathSetValue(prefs, path, newConfig);
	}

	if (newConfig != NULL)  CFRelease(newConfig);
	return ok;
}


__private_extern__ Boolean
__getPrefsEnabled(SCPreferencesRef prefs, CFStringRef path)
{
	CFDictionaryRef config;

	config = SCPreferencesPathGetValue(prefs, path);
	if (isA_CFDictionary(config) && CFDictionaryContainsKey(config, kSCResvInactive)) {
		return FALSE;
	}

	return TRUE;
}


__private_extern__ Boolean
__setPrefsEnabled(SCPreferencesRef      prefs,
		  CFStringRef		path,
		  Boolean		enabled)
{
	CFDictionaryRef		curConfig       = NULL;
	CFMutableDictionaryRef  newConfig       = NULL;
	Boolean			ok		= FALSE;

	/*
	 * preserve current configuration
	 */

	curConfig = SCPreferencesPathGetValue(prefs, path);
	if (curConfig != NULL) {
		if (!isA_CFDictionary(curConfig)) {
			_SCErrorSet(kSCStatusFailed);
			return FALSE;
		}
		newConfig = CFDictionaryCreateMutableCopy(NULL, 0, curConfig);
	} else {
		newConfig = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	}

	if (enabled) {
		// enable
		CFDictionaryRemoveValue(newConfig, kSCResvInactive);
	} else {
		// disable
		CFDictionarySetValue(newConfig, kSCResvInactive, kCFBooleanTrue);
	}

	/*
	 * update configuration
	 */

	if (CFDictionaryGetCount(newConfig) == 0) {
		CFRelease(newConfig);
		newConfig = NULL;
	}

	if (newConfig == NULL) {
		ok = SCPreferencesPathRemoveValue(prefs, path);
	} else {
		ok = SCPreferencesPathSetValue(prefs, path, newConfig);
	}

	if (newConfig != NULL)  CFRelease(newConfig);
	return ok;
}


#define SYSTEMCONFIGURATION_BUNDLE_ID   CFSTR("com.apple.SystemConfiguration")
#define SYSTEMCONFIGURATION_FRAMEWORK   "SystemConfiguration.framework"


static CFDictionaryRef
__copyTemplates()
{
	CFBundleRef     bundle;
	Boolean		ok;
	CFDictionaryRef templates;
	CFURLRef	url;
	CFStringRef     xmlError	= NULL;
	CFDataRef       xmlTemplates    = NULL;

	bundle = CFBundleGetBundleWithIdentifier(SYSTEMCONFIGURATION_BUNDLE_ID);
	if (bundle == NULL) {
		return NULL;
	}

	url = CFBundleCopyResourceURL(bundle, CFSTR("NetworkConfiguration"), CFSTR("plist"), NULL);
	if (url == NULL) {
		return NULL;
	}

	ok = CFURLCreateDataAndPropertiesFromResource(NULL, url, &xmlTemplates, NULL, NULL, NULL);
	CFRelease(url);
	if (!ok || (xmlTemplates == NULL)) {
		return NULL;
	}

	/* convert the XML data into a property list */
	templates = CFPropertyListCreateFromXMLData(NULL, xmlTemplates, kCFPropertyListImmutable, &xmlError);
	CFRelease(xmlTemplates);
	if (templates == NULL) {
		if (xmlError != NULL) {
			SCLog(TRUE, LOG_DEBUG, CFSTR("could not load SCNetworkConfiguration templates: %@"), xmlError);
			CFRelease(xmlError);
		}
		return NULL;
	}

	if (!isA_CFDictionary(templates)) {
		CFRelease(templates);
		return NULL;
	}

	return templates;
}


__private_extern__ CFDictionaryRef
__copyInterfaceTemplate(CFStringRef      interfaceType,
			CFStringRef      childInterfaceType)
{
	CFDictionaryRef interface       = NULL;
	CFDictionaryRef interfaces;
	CFDictionaryRef templates;

	templates = __copyTemplates();
	if (templates == NULL) {
		return NULL;
	}

	interfaces = CFDictionaryGetValue(templates, CFSTR("Interface"));
	if (!isA_CFDictionary(interfaces)) {
		CFRelease(templates);
		return NULL;
	}

	if (childInterfaceType == NULL) {
		interface = CFDictionaryGetValue(interfaces, interfaceType);
	} else {
		CFStringRef     expandedType;

		expandedType = CFStringCreateWithFormat(NULL,
							NULL,
							CFSTR("%@-%@"),
							interfaceType,
							childInterfaceType);
		interface = CFDictionaryGetValue(interfaces, expandedType);
		CFRelease(expandedType);
	}

	if (isA_CFDictionary(interface) && (CFDictionaryGetCount(interface) > 0)) {
		CFRetain(interface);
	} else {
		interface = NULL;
	}

	CFRelease(templates);

	return interface;
}


__private_extern__ CFDictionaryRef
__copyProtocolTemplate(CFStringRef      interfaceType,
		       CFStringRef      childInterfaceType,
		       CFStringRef      protocolType)
{
	CFDictionaryRef interface       = NULL;
	CFDictionaryRef protocol	= NULL;
	CFDictionaryRef protocols;
	CFDictionaryRef templates;

	templates = __copyTemplates();
	if (templates == NULL) {
		return NULL;
	}

	protocols = CFDictionaryGetValue(templates, CFSTR("Protocol"));
	if (!isA_CFDictionary(protocols)) {
		CFRelease(templates);
		return NULL;
	}

	if (childInterfaceType == NULL) {
		interface = CFDictionaryGetValue(protocols, interfaceType);
	} else {
		CFStringRef     expandedType;

		expandedType = CFStringCreateWithFormat(NULL,
							NULL,
							CFSTR("%@-%@"),
							interfaceType,
							childInterfaceType);
		interface = CFDictionaryGetValue(protocols, expandedType);
		CFRelease(expandedType);
	}

	if (isA_CFDictionary(interface)) {
		protocol = CFDictionaryGetValue(interface, protocolType);
		if (isA_CFDictionary(protocol) && (CFDictionaryGetCount(protocol) > 0)) {
			CFRetain(protocol);
		} else {
			protocol = NULL;
		}
	}

	CFRelease(templates);

	return protocol;
}


__private_extern__ Boolean
__createInterface(int s, CFStringRef interface)
{
	struct ifreq	ifr;

	bzero(&ifr, sizeof(ifr));
	(void) _SC_cfstring_to_cstring(interface,
				       ifr.ifr_name,
				       sizeof(ifr.ifr_name),
				       kCFStringEncodingASCII);

	if (ioctl(s, SIOCIFCREATE, &ifr) == -1) {
		SCLog(TRUE,
		      LOG_ERR,
		      CFSTR("could not create interface \"%@\": %s"),
		      interface,
		      strerror(errno));
		return FALSE;
	}

	return TRUE;
}


__private_extern__ Boolean
__destroyInterface(int s, CFStringRef interface)
{
	struct ifreq	ifr;

	bzero(&ifr, sizeof(ifr));
	(void) _SC_cfstring_to_cstring(interface,
				       ifr.ifr_name,
				       sizeof(ifr.ifr_name),
				       kCFStringEncodingASCII);

	if (ioctl(s, SIOCIFDESTROY, &ifr) == -1) {
		SCLog(TRUE,
		      LOG_ERR,
		      CFSTR("could not destroy interface \"%@\": %s"),
		      interface,
		      strerror(errno));
		return FALSE;
	}

	return TRUE;
}


__private_extern__ Boolean
__markInterfaceUp(int s, CFStringRef interface)
{
	struct ifreq	ifr;

	bzero(&ifr, sizeof(ifr));
	(void) _SC_cfstring_to_cstring(interface,
				       ifr.ifr_name,
				       sizeof(ifr.ifr_name),
				       kCFStringEncodingASCII);

	if (ioctl(s, SIOCGIFFLAGS, (caddr_t)&ifr) == -1) {
		SCLog(TRUE,
		      LOG_ERR,
		      CFSTR("could not get flags for interface \"%@\": %s"),
		      interface,
		      strerror(errno));
		return FALSE;
	}

	ifr.ifr_flags |= IFF_UP;
	if (ioctl(s, SIOCSIFFLAGS, (caddr_t)&ifr) == -1) {
		SCLog(TRUE,
		      LOG_ERR,
		      CFSTR("could not set flags for interface \"%@\": %s"),
		      interface,
		      strerror(errno));
		return FALSE;
	}

	return TRUE;
}
