/*
 * Copyright (c) 2004, 2005 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
 *
 * August 5, 2004			Allan Nathanson <ajn@apple.com>
 * - initial revision
 */


#include "scutil.h"
#include "net.h"

#include <SystemConfiguration/LinkConfiguration.h>


/* -------------------- */


static CFArrayRef
_copy_interfaces()
{
	CFMutableArrayRef	interfaces;
	CFArrayRef		real_interfaces;

	real_interfaces = SCNetworkInterfaceCopyAll();
	if (real_interfaces == NULL) {
		SCPrint(TRUE, stdout, CFSTR("%s\n"), SCErrorString(SCError()));
		return NULL;
	}

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

	// include real interfaces
	CFArrayAppendArray(interfaces,
			   real_interfaces,
			   CFRangeMake(0, CFArrayGetCount(real_interfaces)));
	CFRelease(real_interfaces);

	// include pseudo interfaces
	CFArrayAppendValue(interfaces, kSCNetworkInterfaceIPv4);

	// include interfaces that we have created
	if (new_interfaces != NULL) {
		CFArrayAppendArray(interfaces,
				   new_interfaces,
				   CFRangeMake(0, CFArrayGetCount(new_interfaces)));
	}

	return (CFArrayRef)interfaces;
}


__private_extern__
SCNetworkInterfaceRef
_find_interface(char *match)
{
	Boolean			allowIndex	= TRUE;
	CFIndex			i;
	CFIndex			n;
	CFStringRef		select_name	= NULL;
	SCNetworkInterfaceRef	selected	= NULL;

	if (strcasecmp(match, "$child") == 0) {
		if (net_interface == NULL) {
			SCPrint(TRUE, stdout, CFSTR("interface not selected\n"));
			goto done;
		}

		selected = SCNetworkInterfaceGetInterface(net_interface);
		if(selected == NULL) {
			SCPrint(TRUE, stdout, CFSTR("no child interface\n"));
		}

		goto done;
	} else if (strcasecmp(match, "$service") == 0) {
		if (net_service == NULL) {
			SCPrint(TRUE, stdout, CFSTR("service not selected\n"));
			goto done;
		}

		selected = SCNetworkServiceGetInterface(net_service);
		if(selected == NULL) {
			SCPrint(TRUE, stdout, CFSTR("no interface for service\n"));
		}

		goto done;
	}

	if (interfaces == NULL) {
		interfaces = _copy_interfaces();
		if (interfaces == NULL) {
			return NULL;
		}
		allowIndex = FALSE;
	}

	// try to select the interface by its display name

	select_name = CFStringCreateWithCString(NULL, match, kCFStringEncodingUTF8);

	n = CFArrayGetCount(interfaces);
	for (i = 0; i < n; i++) {
		SCNetworkInterfaceRef	interface;
		CFStringRef		interfaceName;

		interface = CFArrayGetValueAtIndex(interfaces, i);
		interfaceName = SCNetworkInterfaceGetLocalizedDisplayName(interface);
		if ((interfaceName != NULL) && CFEqual(select_name, interfaceName)) {
			if (selected == NULL) {
				selected = interface;
			} else {
				// if multiple interfaces match
				selected = NULL;
				SCPrint(TRUE, stdout, CFSTR("multiple interfaces match\n"));
				goto done;
			}
		}
	}

	if (selected != NULL) {
		goto done;
	}

	// try to select the interface by its BSD name

	for (i = 0; i < n; i++) {
		SCNetworkInterfaceRef	interface;
		CFStringRef		bsd_name	= NULL;

		interface = CFArrayGetValueAtIndex(interfaces, i);
                while ((interface != NULL) && (bsd_name == NULL)) {
                        bsd_name = SCNetworkInterfaceGetBSDName(interface);
                        if (bsd_name == NULL) {
                                interface = SCNetworkInterfaceGetInterface(interface);
                        }
                }

		if ((bsd_name != NULL) && CFEqual(select_name, bsd_name)) {
			if (selected == NULL) {
				selected = interface;
			} else {
				// if multiple interfaces match
				selected = NULL;
				SCPrint(TRUE, stdout, CFSTR("multiple interfaces match\n"));
				goto done;
			}
		}
	}

	if (selected != NULL) {
		goto done;
	}

	// try to select the interface by its interface type

	for (i = 0; i < n; i++) {
		SCNetworkInterfaceRef	interface;
		CFStringRef		interfaceType;

		interface = CFArrayGetValueAtIndex(interfaces, i);
		interfaceType = SCNetworkInterfaceGetInterfaceType(interface);
		if (CFEqual(select_name, interfaceType)) {
			if (selected == NULL) {
				selected = interface;
			} else {
				// if multiple interfaces match
				selected = NULL;
				SCPrint(TRUE, stdout, CFSTR("multiple interfaces match\n"));
				goto done;
			}
		}
	}

	if (selected != NULL) {
		goto done;
	}

	if (allowIndex) {
		char	*end;
		char	*str	= match;
		long	val;

		// try to select the interface by its index

		errno = 0;
		val = strtol(str, &end, 10);
		if ((*str != '\0') &&
		    ((*end == '\0') || (*end == '.')) &&
		    (errno == 0)) {
			if ((val > 0) && (val <= n)) {
				selected = CFArrayGetValueAtIndex(interfaces, val - 1);

				if (*end == '.') {
					str = end + 1;
					val = strtol(str, &end, 10);
					if ((*str != '\0') && (*end == '\0') && (errno == 0)) {
						while (val-- > 0) {
							selected = SCNetworkInterfaceGetInterface(selected);
							if (selected == NULL) {
								break;
							}
						}
					}
				}
			}
		}
	}

	if (selected != NULL) {
		goto done;
	}

	SCPrint(TRUE, stdout, CFSTR("no match\n"));

    done :

	if (select_name != NULL) CFRelease(select_name);
	return selected;
}


/* -------------------- */


__private_extern__
void
create_interface(int argc, char **argv)
{
	SCNetworkInterfaceRef	interface;
	CFStringRef		interfaceName;
	CFStringRef		interfaceType;
	SCNetworkInterfaceRef	new_interface;

	if (argc < 1) {
		SCPrint(TRUE, stdout, CFSTR("what interface type?\n"));
		return;
	}

	interfaceType = CFStringCreateWithCString(NULL, argv[0], kCFStringEncodingUTF8);

	if (CFEqual(interfaceType, kSCNetworkInterfaceTypeVLAN)) {
// xxxxx
SCPrint(TRUE, stdout, CFSTR("vlan creation not yet supported\n"));
goto done;
	} else if (CFEqual(interfaceType, kSCNetworkInterfaceTypeBond)) {
// xxxxx
SCPrint(TRUE, stdout, CFSTR("bond creation not yet supported\n"));
goto done;
	} else {
		if (argc < 2) {
			if (net_interface == NULL) {
				SCPrint(TRUE, stdout, CFSTR("no network interface selected\n"));
				goto done;
			}

			interface = net_interface;
		} else {
			interface = _find_interface(argv[1]);
		}

		if (interface == NULL) {
			return;
		}

		new_interface = SCNetworkInterfaceCreateWithInterface(interface, interfaceType);
		if (new_interface == NULL) {
			SCPrint(TRUE, stdout, CFSTR("%s\n"), SCErrorString(SCError()));
			goto done;
		}
	}

	if (new_interfaces == NULL) {
		new_interfaces = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	}
	CFArrayAppendValue(new_interfaces, new_interface);

	if (net_interface != NULL) CFRelease(net_interface);
	net_interface = new_interface;

	interfaceName = SCNetworkInterfaceGetLocalizedDisplayName(net_interface);
	if (interfaceName == NULL) {
		interfaceName = SCNetworkInterfaceGetBSDName(net_interface);
	}
	if (interfaceName == NULL) {
		interfaceName = SCNetworkInterfaceGetInterfaceType(net_interface);
	}
	SCPrint(TRUE, stdout, CFSTR("interface \"%@\" created and selected\n"), interfaceName);

    done :

	CFRelease(interfaceType);
	return;
}


/* -------------------- */


__private_extern__
void
select_interface(int argc, char **argv)
{
	SCNetworkInterfaceRef	interface;

	interface = _find_interface(argv[0]);

	if (interface != NULL) {
		CFStringRef	interfaceName;

		if (net_interface != NULL) CFRelease(net_interface);
		net_interface = CFRetain(interface);

		interfaceName = SCNetworkInterfaceGetLocalizedDisplayName(interface);
		if (interfaceName == NULL) {
			interfaceName = SCNetworkInterfaceGetBSDName(interface);
		}
		if (interfaceName == NULL) {
			interfaceName = SCNetworkInterfaceGetInterfaceType(interface);
		}

		SCPrint(TRUE, stdout, CFSTR("interface \"%@\" selected\n"), interfaceName);
	}

	return;
}


/* -------------------- */


__private_extern__
void
_show_interface(SCNetworkInterfaceRef interface, CFStringRef prefix, Boolean showChild)
{
	CFDictionaryRef configuration;
	CFStringRef	if_bsd_name;
	CFStringRef	if_localized_name;
	CFStringRef	if_mac_address;
	CFStringRef	if_type;
	CFArrayRef	supported;

	if_localized_name = SCNetworkInterfaceGetLocalizedDisplayName(interface);
	if (if_localized_name != NULL) {
		SCPrint(TRUE, stdout, CFSTR("%@  name                 = %@\n"), prefix, if_localized_name);
	}

	if_bsd_name = SCNetworkInterfaceGetBSDName(interface);
	if (if_bsd_name != NULL) {
		SCPrint(TRUE, stdout, CFSTR("%@  interface name       = %@\n"), prefix, if_bsd_name);
	}

	if_type = SCNetworkInterfaceGetInterfaceType(interface);
	SCPrint(TRUE, stdout, CFSTR("%@  type                 = %@\n"), prefix, if_type);

	if_mac_address = SCNetworkInterfaceGetHardwareAddressString(interface);
	if (if_mac_address != NULL) {
		SCPrint(TRUE, stdout, CFSTR("%@  address              = %@\n"), prefix, if_mac_address);
	}

	configuration = SCNetworkInterfaceGetConfiguration(interface);
	if ((configuration != NULL) &&
	    CFDictionaryContainsKey(configuration, kSCResvInactive)) {
		configuration = NULL;
	}

	if (if_bsd_name != NULL) {
		CFArrayRef	available;
		CFDictionaryRef	active;
		int		mtu_cur;
		int		mtu_min;
		int		mtu_max;

		if (NetworkInterfaceCopyMTU(if_bsd_name, &mtu_cur, &mtu_min, &mtu_max)) {
			char	isCurrent	= '*';

			if (configuration != NULL) {
				int		mtu_req;
				CFNumberRef	num;

				num = CFDictionaryGetValue(configuration, kSCPropNetEthernetMTU);
				if (isA_CFNumber(num)) {
					CFNumberGetValue(num, kCFNumberIntType, &mtu_req);
					if (mtu_cur != mtu_req) {
						mtu_cur = mtu_req;
						isCurrent = ' ';
					}
				}
			}

			SCPrint(TRUE, stdout, CFSTR("%@  mtu                %c = %ld (%ld < n < %ld)\n"),
				prefix,
				isCurrent,
				mtu_cur,
				mtu_min,
				mtu_max);
		}

		if (NetworkInterfaceCopyMediaOptions(if_bsd_name, NULL, &active, &available, TRUE)) {
			char		isCurrent	= ' ';
			CFArrayRef	options		= NULL;
			CFArrayRef	options_req	= NULL;
			CFStringRef	subtype		= NULL;
			CFStringRef	subtype_req	= NULL;

			if (configuration != NULL) {
				subtype_req = CFDictionaryGetValue(configuration, kSCPropNetEthernetMediaSubType);
				options_req = CFDictionaryGetValue(configuration, kSCPropNetEthernetMediaOptions);
			}

			if (subtype_req == NULL) {
				subtype_req = CFSTR("autoselect");
			}

			if (active != NULL) {
				subtype = CFDictionaryGetValue(active, kSCPropNetEthernetMediaSubType);
				options = CFDictionaryGetValue(active, kSCPropNetEthernetMediaOptions);
			}

			if (subtype != NULL) {
				if (((subtype_req != NULL) &&
				     CFEqual(subtype, subtype_req)) &&
				    ((options == options_req) ||
				     ((options != NULL) &&
				      (options_req != NULL) &&
				      CFEqual(options, options_req)))
				   ) {
					isCurrent = '*';
				} else if ((subtype_req == NULL) ||
					   ((subtype_req != NULL) &&
					    CFEqual(subtype_req, CFSTR("autoselect")))) {
					// if requested subtype not specified or "autoselect"
					isCurrent = '*';
				}
			}

			if (subtype_req != NULL) {
				SCPrint(TRUE, stdout, CFSTR("%@  media              %c = %@"),
					prefix,
					isCurrent,
					subtype_req);

				if ((options_req != NULL) &&
				    (CFArrayGetCount(options_req) > 0)) {
					CFStringRef	options_str;

					options_str = CFStringCreateByCombiningStrings(NULL, options_req, CFSTR(","));
					SCPrint(TRUE, stdout, CFSTR(" <%@>"), options_str);
					CFRelease(options_str);
				}

				SCPrint(TRUE, stdout, CFSTR("\n"));
			}

			SCPrint(TRUE, stdout, CFSTR("\n"));

			if (available != NULL) {
				CFIndex		i;
				CFIndex		n_subtypes;
				CFArrayRef	subtypes;

				subtypes   = NetworkInterfaceCopyMediaSubTypes(available);
				n_subtypes = (subtypes != NULL) ? CFArrayGetCount(subtypes) : 0;
				for (i = 0; i < n_subtypes; i++) {
					CFIndex		j;
					CFIndex		n_subtype_options;
					CFStringRef	subtype;
					CFArrayRef	subtype_options;

					subtype = CFArrayGetValueAtIndex(subtypes, i);
					subtype_options = NetworkInterfaceCopyMediaSubTypeOptions(available, subtype);
					n_subtype_options = (subtype_options != NULL) ? CFArrayGetCount(subtype_options) : 0;
					for (j = 0; j < n_subtype_options; j++) {
						char		isCurrent	= ' ';
						CFArrayRef	options;

						options = CFArrayGetValueAtIndex(subtype_options, j);

						if (((subtype_req != NULL) &&
						     CFEqual(subtype, subtype_req)) &&
						    ((options == options_req) ||
						     ((options != NULL) &&
						      (options_req != NULL) &&
						      CFEqual(options, options_req)))
						   ) {
							isCurrent = '*';
						}

						SCPrint(TRUE, stdout, CFSTR("%@  %s    %c = %@"),
							prefix,
							((i == 0) && (j == 0)) ? "supported media" : "               ",
							isCurrent,
							subtype);

						if ((options != NULL) &&
						    (CFArrayGetCount(options) > 0)) {
							CFStringRef	options_str;

							options_str = CFStringCreateByCombiningStrings(NULL, options, CFSTR(","));
							SCPrint(TRUE, stdout, CFSTR(" <%@>"), options_str);
							CFRelease(options_str);
						}

						SCPrint(TRUE, stdout, CFSTR("\n"));
					}
					CFRelease(subtype_options);
				}
			}
		} else {
			SCPrint(TRUE, stdout, CFSTR("\n"));
		}
	}

	supported = SCNetworkInterfaceGetSupportedInterfaceTypes(interface);
	SCPrint(TRUE, stdout, CFSTR("%@  supported interfaces = "), prefix);
	if (supported != NULL) {
		CFIndex i;
		CFIndex n	= CFArrayGetCount(supported);

		for (i = 0; i < n; i++) {
			SCPrint(TRUE, stdout, CFSTR("%s%@"),
				(i == 0) ? "" : ", ",
				CFArrayGetValueAtIndex(supported, i));
		}
	}
	SCPrint(TRUE, stdout, CFSTR("\n"));

	supported = SCNetworkInterfaceGetSupportedProtocolTypes(interface);
	SCPrint(TRUE, stdout, CFSTR("%@  supported protocols  = "), prefix);
	if (supported != NULL) {
		CFIndex i;
		CFIndex n	= CFArrayGetCount(supported);

		for (i = 0; i < n; i++) {
			SCPrint(TRUE, stdout, CFSTR("%s%@"),
				(i == 0) ? "" : ", ",
				CFArrayGetValueAtIndex(supported, i));
		}
	}
	SCPrint(TRUE, stdout, CFSTR("\n"));

	if (configuration != NULL) {
		CFMutableDictionaryRef	effective;

		effective = CFDictionaryCreateMutableCopy(NULL, 0, configuration);

		// remove known (and already reported) interface configuration keys
		if (CFDictionaryContainsKey(effective, kSCResvInactive)) {
			CFDictionaryRemoveAllValues(effective);
		}
		CFDictionaryRemoveValue(effective, kSCPropNetEthernetMTU);
		CFDictionaryRemoveValue(effective, kSCPropNetEthernetMediaSubType);
		CFDictionaryRemoveValue(effective, kSCPropNetEthernetMediaOptions);

		if (CFDictionaryGetCount(effective) > 0) {
			SCPrint(TRUE, stdout, CFSTR("\n%@  per-interface configuration\n"), prefix);
			_show_entity(configuration, prefix);
		}

		CFRelease(effective);
	}

	if (_sc_debug) {
		SCPrint(TRUE, stdout, CFSTR("\n%@\n"), interface);
	}

	interface = SCNetworkInterfaceGetInterface(interface);
	if (interface != NULL) {
		CFStringRef	newPrefix;

		newPrefix = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@  "), prefix);
		SCPrint(TRUE, stdout, CFSTR("\n%@child interface\n"), newPrefix);
		_show_interface(interface, newPrefix, showChild);
		CFRelease(newPrefix);
	}

	return;
}


/* -------------------- */


static Boolean
validateMediaOptions(CFStringRef interfaceName, CFMutableDictionaryRef newConfiguration)
{
	Boolean		ok	= TRUE;
	CFNumberRef	mtu;
	CFArrayRef	options;
	CFStringRef	subtype;

	mtu = CFDictionaryGetValue(newConfiguration, kSCPropNetEthernetMTU);
	if (isA_CFNumber(mtu)) {
		int	mtu_max;
		int	mtu_min;
		int	mtu_val;

		if (!NetworkInterfaceCopyMTU(interfaceName, NULL, &mtu_min, &mtu_max)) {
			SCPrint(TRUE, stdout, CFSTR("cannot set MTU\n"));
			return FALSE;
		}

		if (!CFNumberGetValue(mtu, kCFNumberIntType, &mtu_val) ||
		    (mtu_val < mtu_min) ||
		    (mtu_val > mtu_max)) {
			SCPrint(TRUE, stdout, CFSTR("mtu out of range\n"));
			return FALSE;
		}
	}

	subtype = CFDictionaryGetValue(newConfiguration, kSCPropNetEthernetMediaSubType);
	options = CFDictionaryGetValue(newConfiguration, kSCPropNetEthernetMediaOptions);

	if (subtype != NULL) {
		CFArrayRef	available	= NULL;
		CFArrayRef	config_options	= options;
		CFArrayRef	subtypes	= NULL;
		CFArrayRef	subtype_options	= NULL;

		ok = FALSE;

		if (options == NULL) {
			config_options = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
		}

		if (interfaceName == NULL) {
			SCPrint(TRUE, stdout, CFSTR("media type / options not available\n"));
			goto checked;
		}

		if (!NetworkInterfaceCopyMediaOptions(interfaceName, NULL, NULL, &available, FALSE)) {
			SCPrint(TRUE, stdout, CFSTR("media type / options not available\n"));
			goto checked;
		}

		if (available == NULL) {
			goto checked;
		}

		subtypes = NetworkInterfaceCopyMediaSubTypes(available);
		if ((subtypes == NULL) ||
		    !CFArrayContainsValue(subtypes,
					 CFRangeMake(0, CFArrayGetCount(subtypes)),
					 subtype)) {
			SCPrint(TRUE, stdout, CFSTR("media type not valid\n"));
			goto checked;
		}

		subtype_options = NetworkInterfaceCopyMediaSubTypeOptions(available, subtype);
		if ((subtype_options == NULL) ||
		    !CFArrayContainsValue(subtype_options,
					  CFRangeMake(0, CFArrayGetCount(subtype_options)),
					  config_options)) {
			SCPrint(TRUE, stdout, CFSTR("media options not valid for \"%@\"\n"), subtype);
			goto checked;
		}

		if (options == NULL) {
			CFDictionarySetValue(newConfiguration, kSCPropNetEthernetMediaOptions, config_options);
		}

		ok = TRUE;

	    checked :

		if (available       != NULL)	CFRelease(available);
		if (subtypes        != NULL)	CFRelease(subtypes);
		if (subtype_options != NULL)	CFRelease(subtype_options);
		if (options         == NULL)	CFRelease(config_options);
	} else {
		if (options != NULL) {
			SCPrint(TRUE, stdout, CFSTR("media type and options must both be specified\n"));
			return FALSE;
		}
	}

	return ok;
}


/* -------------------- */


__private_extern__
void
show_interfaces(int argc, char **argv)
{
	CFIndex		i;
	CFIndex		n;

	if (interfaces != NULL) CFRelease(interfaces);
	interfaces = _copy_interfaces();
	if (interfaces == NULL) {
		return;
	}

	n = CFArrayGetCount(interfaces);
	for (i = 0; i < n; i++) {
		CFIndex			childIndex	= 0;
		SCNetworkInterfaceRef	interface;

		interface = CFArrayGetValueAtIndex(interfaces, i);
		do {
			CFStringRef	interfaceName;
			char		isSelected;

			interfaceName = SCNetworkInterfaceGetLocalizedDisplayName(interface);
			if (interfaceName == NULL) {
				interfaceName = SCNetworkInterfaceGetBSDName(interface);
			}
			if (interfaceName == NULL) {
				interfaceName = SCNetworkInterfaceGetInterfaceType(interface);
			}

			isSelected = ' ';
			if ((net_interface != NULL) && CFEqual(interface, net_interface)) {
				isSelected = '>';
			}

			if (childIndex == 0) {
				SCPrint(TRUE, stdout, CFSTR("%c%2d: %@\n"),
					isSelected,
					i + 1,
					interfaceName);
			} else {
				SCPrint(TRUE, stdout, CFSTR("%c%2d.%d: %@\n"),
					isSelected,
					i + 1,
					childIndex,
					interfaceName);
			}

			interface = SCNetworkInterfaceGetInterface(interface);
			childIndex++;
		} while (interface != NULL);
	}

	return;
}


/* -------------------- */


static Boolean
set_interface_bond(int argc, char **argv, CFMutableDictionaryRef newConfiguration)
{
// xxxxx ("+device", "-device")
SCPrint(TRUE, stdout, CFSTR("bond interface management not yet supported\n"));
	return FALSE;
}


/* -------------------- */


static options airportOptions[] = {
	{ "mtu"       , NULL, isNumber     , &kSCPropNetEthernetMTU         , NULL, NULL },
	{ "media"     , NULL, isString     , &kSCPropNetEthernetMediaSubType, NULL, NULL },
	{ "mediaopt"  , NULL, isStringArray, &kSCPropNetEthernetMediaOptions, NULL, NULL },

	{ "?"         , NULL , isHelp     , NULL                            , NULL,
	    "\nAirPort configuration commands\n\n"
	    " set interface [mtu n] [media type] [mediaopts opts]\n"
	}
};
#define	N_AIRPORT_OPTIONS	(sizeof(airportOptions) / sizeof(airportOptions[0]))


static Boolean
set_interface_airport(int argc, char **argv, CFMutableDictionaryRef newConfiguration)
{
	CFStringRef	interfaceName;
	Boolean		ok;

	interfaceName = SCNetworkInterfaceGetBSDName(net_interface);
	if (interfaceName == NULL) {
		SCPrint(TRUE, stdout, CFSTR("no BSD interface\n"));
		return FALSE;
	}

	ok = _process_options(airportOptions, N_AIRPORT_OPTIONS, argc, argv, newConfiguration);
	if (ok) {
		// validate configuration
		if (!validateMediaOptions(interfaceName, newConfiguration)) {
			return FALSE;
		}
	}

	return ok;
}


/* -------------------- */


static options ethernetOptions[] = {
	{ "mtu"       , NULL, isNumber     , &kSCPropNetEthernetMTU         , NULL, NULL },
	{ "media"     , NULL, isString     , &kSCPropNetEthernetMediaSubType, NULL, NULL },
	{ "mediaopt"  , NULL, isStringArray, &kSCPropNetEthernetMediaOptions, NULL, NULL },

	{ "?"         , NULL , isHelp     , NULL                            , NULL,
	    "\nEthernet configuration commands\n\n"
	    " set interface [mtu n] [media type] [mediaopts opts]\n"
	}
};
#define	N_ETHERNET_OPTIONS	(sizeof(ethernetOptions) / sizeof(ethernetOptions[0]))


static Boolean
set_interface_ethernet(int argc, char **argv, CFMutableDictionaryRef newConfiguration)
{
	CFStringRef	interfaceName;
	Boolean		ok;

	interfaceName = SCNetworkInterfaceGetBSDName(net_interface);
	if (interfaceName == NULL) {
		SCPrint(TRUE, stdout, CFSTR("no BSD interface\n"));
		return FALSE;
	}

	ok = _process_options(ethernetOptions, N_ETHERNET_OPTIONS, argc, argv, newConfiguration);
	if (ok) {
		// validate configuration
		if (!validateMediaOptions(interfaceName, newConfiguration)) {
			return FALSE;
		}
	}

	return ok;
}


/* -------------------- */


static options firewireOptions[] = {
	{ "mtu"       , NULL, isNumber     , &kSCPropNetEthernetMTU         , NULL, NULL },
	{ "media"     , NULL, isString     , &kSCPropNetEthernetMediaSubType, NULL, NULL },
	{ "mediaopt"  , NULL, isStringArray, &kSCPropNetEthernetMediaOptions, NULL, NULL },

	{ "?"         , NULL , isHelp     , NULL                            , NULL,
	    "\nFireWire configuration commands\n\n"
	    " set interface [mtu n] [media type] [mediaopts opts]\n"
	}
};
#define	N_FIREWIRE_OPTIONS	(sizeof(firewireOptions) / sizeof(firewireOptions[0]))


static Boolean
set_interface_firewire(int argc, char **argv, CFMutableDictionaryRef newConfiguration)
{
	CFStringRef	interfaceName;
	Boolean		ok;

	interfaceName = SCNetworkInterfaceGetBSDName(net_interface);
	if (interfaceName == NULL) {
		SCPrint(TRUE, stdout, CFSTR("no BSD interface\n"));
		return FALSE;
	}

	ok = _process_options(firewireOptions, N_FIREWIRE_OPTIONS, argc, argv, newConfiguration);
	if (ok) {
		// validate configuration
		if (!validateMediaOptions(interfaceName, newConfiguration)) {
			return FALSE;
		}
	}

	return ok;
}


/* -------------------- */


static selections modemDialSelections[] = {
	{ CFSTR("ignore"), &kSCValNetModemDialModeIgnoreDialTone , 0 },
	{ CFSTR("manual"), &kSCValNetModemDialModeManual         , 0 },
	{ CFSTR("wait")  , &kSCValNetModemDialModeWaitForDialTone, 0 },
	{ NULL           , NULL                                  , 0 }
};

static options modemOptions[] = {
	{ "ConnectionScript"             , "script", isString   , &kSCPropNetModemConnectionScript           , NULL, NULL                        },
	{ "DialMode"                     , "mode"  , isChooseOne, &kSCPropNetModemDialMode                   , NULL, (void *)modemDialSelections },
	{ "CallWaiting"                  , NULL    , isBoolean  , &kSCPropNetModemHoldEnabled                , NULL, NULL                        },
	{ "CallWaitingAlert"             , NULL    , isBoolean  , &kSCPropNetModemHoldCallWaitingAudibleAlert, NULL, NULL                        },
	{ "CallWaitingDisconnectOnAnswer", NULL    , isBoolean  , &kSCPropNetModemHoldDisconnectOnAnswer     , NULL, NULL                        },
	{ "DataCompression"              , NULL    , isBoolean  , &kSCPropNetModemDataCompression            , NULL, NULL                        },
	{ "ErrorCorrection"              , NULL    , isBoolean  , &kSCPropNetModemErrorCorrection            , NULL, NULL                        },
	{ "HoldReminder"                 , NULL    , isBoolean  , &kSCPropNetModemHoldReminder               , NULL, NULL                        },
	{ "HoldReminderTime"             , "time"  , isNumber   , &kSCPropNetModemHoldReminderTime           , NULL, NULL                        },
	{ "PulseDial"                    , NULL    , isBoolean  , &kSCPropNetModemPulseDial                  , NULL, NULL                        },
	{ "Speaker"                      , NULL    , isBoolean  , &kSCPropNetModemSpeaker                    , NULL, NULL                        },

	{ "?"                            , NULL    , isHelp     , NULL                                       , NULL,
	    "\nModem configuration commands\n\n"
	    " set interface [ConnectionScript connection-script]\n"
	    " set interface [CallWaiting {enable|disable}]\n"
	    " set interface [CallWaitingAlert {enable|disable}]\n"
	    " set interface [CallWaitingDisconnectOnAnswer {enable|disable}]\n"
	    " set interface [DialMode {ignore|wait}]\n"
	    " set interface [DataCompression {enable|disable}]\n"
	    " set interface [ErrorCorrection {enable|disable}]\n"
	    " set interface [HoldReminder {enable|disable}]\n"
	    " set interface [HoldReminderTime n]\n"
	    " set interface [PulseDial {enable|disable}]\n"
	    " set interface [Speaker {enable|disable}]"
	}
};
#define	N_MODEM_OPTIONS	(sizeof(modemOptions) / sizeof(modemOptions[0]))


static Boolean
set_interface_modem(int argc, char **argv, CFMutableDictionaryRef newConfiguration)
{
	Boolean	ok;

	ok = _process_options(modemOptions, N_MODEM_OPTIONS, argc, argv, newConfiguration);
	return ok;
}


/* -------------------- */


static int
__doPPPAuthPW(CFStringRef key, const char *description, void *info, int argc, char **argv, CFMutableDictionaryRef newConfiguration)
{
	if (argc < 1) {
		SCPrint(TRUE, stdout, CFSTR("PPP password not specified\n"));
		return -1;
	}

	if (strlen(argv[0]) > 0) {
		CFStringRef	encryptionType;

		encryptionType = CFDictionaryGetValue(newConfiguration, kSCPropNetPPPAuthPasswordEncryption);
		if (encryptionType == NULL) {
			CFIndex			n;
			CFMutableDataRef	pw;
			CFStringRef		str;

			str = CFStringCreateWithCString(NULL, argv[0], kCFStringEncodingUTF8);
			n = CFStringGetLength(str);
			pw = CFDataCreateMutable(NULL, n * sizeof(UniChar));
			CFDataSetLength(pw, n * sizeof(UniChar));
			CFStringGetCharacters(str,
					      CFRangeMake(0, n),
					      (UniChar *)CFDataGetMutableBytePtr(pw));
			CFRelease(str);

			CFDictionarySetValue(newConfiguration, key, pw);
			CFRelease(pw);
		} else {
			SCPrint(TRUE, stdout, CFSTR("PPP password type \"%@\" not supported\n"), encryptionType);
			return -1;
		}
	} else {
		CFDictionaryRemoveValue(newConfiguration, key);
	}

	return 1;
}


static int
__doPPPAuthPWType(CFStringRef key, const char *description, void *info, int argc, char **argv, CFMutableDictionaryRef newConfiguration)
{
	if (argc < 1) {
		SCPrint(TRUE, stdout, CFSTR("PPP password type mode not specified\n"));
		return -1;
	}

	if (strlen(argv[0]) > 0) {
		if (strcasecmp(argv[0], "keychain") == 0) {
			CFDictionarySetValue(newConfiguration, key, kSCValNetPPPAuthPasswordEncryptionKeychain);
		} else {
			SCPrint(TRUE, stdout, CFSTR("invalid password type\n"));
			return -1;
		}
	} else {
		CFDictionaryRemoveValue(newConfiguration, key);
	}

	// encryption type changed, reset password
	CFDictionaryRemoveValue(newConfiguration, kSCPropNetPPPAuthPassword);

	return 1;
}


static selections authPromptSelections[] = {
	{ CFSTR("before"), &kSCValNetPPPAuthPromptBefore, 0 },
	{ CFSTR("after") , &kSCValNetPPPAuthPromptAfter , 0 },
	{ NULL    , NULL                                , 0 }
};


static selections authProtocolSelections[] = {
	{ CFSTR("CHAP")   , &kSCValNetPPPAuthProtocolCHAP   , 0 },
	{ CFSTR("EAP")    , &kSCValNetPPPAuthProtocolEAP    , 0 },
	{ CFSTR("MSCHAP1"), &kSCValNetPPPAuthProtocolMSCHAP1, 0 },
	{ CFSTR("MSCHAP2"), &kSCValNetPPPAuthProtocolMSCHAP2, 0 },
	{ CFSTR("PAP")    , &kSCValNetPPPAuthProtocolPAP    , 0 },
	{ NULL            , NULL                            , 0 }
};


static options pppOptions[] = {
	{ "ACSP"                      , NULL          , isBoolean        , &kSCPropNetPPPACSPEnabled               , NULL             , NULL                           },
	{ "ConnectTime"               , "?time"       , isNumber         , &kSCPropNetPPPConnectTime               , NULL             , NULL                           },
	{ "DialOnDemand"              , NULL          , isBoolean        , &kSCPropNetPPPDialOnDemand              , NULL             , NULL                           },
	{ "DisconnectOnFastUserSwitch", NULL          , isBoolean        , &kSCPropNetPPPDisconnectOnFastUserSwitch, NULL             , NULL                           },
	{ "DisconnectOnIdle"          , NULL          , isBoolean        , &kSCPropNetPPPDisconnectOnIdle          , NULL             , NULL                           },
	{ "DisconnectOnIdleTimer"     , "timeout"     , isNumber         , &kSCPropNetPPPDisconnectOnIdleTimer     , NULL             , NULL                           },
	{ "DisconnectOnLogout"        , NULL          , isBoolean        , &kSCPropNetPPPDisconnectOnLogout        , NULL             , NULL                           },
	{ "DisconnectOnSleep"         , NULL          , isBoolean        , &kSCPropNetPPPDisconnectOnSleep         , NULL             , NULL                           },
	{ "DisconnectTime"            , "?time"       , isNumber         , &kSCPropNetPPPDisconnectTime            , NULL             , NULL                           },
	{ "IdleReminder"              , NULL          , isBoolean        , &kSCPropNetPPPIdleReminder              , NULL             , NULL                           },
	{ "IdleReminderTimer"         , "time"        , isNumber         , &kSCPropNetPPPIdleReminderTimer         , NULL             , NULL                           },
	{ "Logfile"                   , "path"        , isString         , &kSCPropNetPPPLogfile                   , NULL             , NULL                           },
	{ "Plugins"                   , "plugin"      , isStringArray    , &kSCPropNetPPPPlugins                   , NULL             , NULL                           },
	{ "RetryConnectTime"          , "time"        , isNumber         , &kSCPropNetPPPRetryConnectTime          , NULL             , NULL                           },
	{ "SessionTimer"              , "time"        , isNumber         , &kSCPropNetPPPSessionTimer              , NULL             , NULL                           },
	{ "UseSessionTimer"           , NULL          , isBoolean        , &kSCPropNetPPPUseSessionTimer           , NULL             , NULL                           },
	{ "VerboseLogging"            , NULL          , isBoolean        , &kSCPropNetPPPVerboseLogging            , NULL             , NULL                           },

	// --- Auth: ---
	{ "AuthEAPPlugins"            , "plugin"      , isStringArray    , &kSCPropNetPPPAuthEAPPlugins            , NULL             , NULL                           },
	{ "AuthName"                  , "account"     , isString         , &kSCPropNetPPPAuthName                  , NULL             , NULL                           },
	{   "Account"                 , "account"     , isString         , &kSCPropNetPPPAuthName                  , NULL             , NULL                           },
	{ "AuthPassword"              , "password"    , isOther          , &kSCPropNetPPPAuthPassword              , __doPPPAuthPW    , NULL                           },
	{   "Password"                , "password"    , isOther          , &kSCPropNetPPPAuthPassword              , __doPPPAuthPW    , NULL                           },
	{ "AuthPasswordEncryption"    , "type"        , isOther          , &kSCPropNetPPPAuthPasswordEncryption    , __doPPPAuthPWType, NULL                           },
	{ "AuthPrompt"                , "before/after", isChooseOne      , &kSCPropNetPPPAuthPrompt                , NULL             , (void *)authPromptSelections   },
	{ "AuthProtocol"              , "protocol"    , isChooseMultiple , &kSCPropNetPPPAuthProtocol              , NULL             , (void *)authProtocolSelections },

	// --- Comm: ---
	{ "CommRemoteAddress"         , "phone#"      , isString         , &kSCPropNetPPPCommRemoteAddress         , NULL             , NULL                           },
	{ "CommAlternateRemoteAddress", "phone#"      , isString         , &kSCPropNetPPPCommAlternateRemoteAddress, NULL             , NULL                           },
	{ "CommConnectDelay"          , "time"        , isNumber         , &kSCPropNetPPPCommConnectDelay          , NULL             , NULL                           },
	{ "CommDisplayTerminalWindow" , NULL          , isBoolean        , &kSCPropNetPPPCommDisplayTerminalWindow , NULL             , NULL                           },
	{ "CommRedialCount"           , "retry count" , isNumber         , &kSCPropNetPPPCommRedialCount           , NULL             , NULL                           },
	{ "CommRedialEnabled"         , NULL          , isBoolean        , &kSCPropNetPPPCommRedialEnabled         , NULL             , NULL                           },
	{ "CommRedialInterval"        , "retry delay" , isNumber         , &kSCPropNetPPPCommRedialInterval        , NULL             , NULL                           },
	{ "CommTerminalScript"        , "script"      , isString         , &kSCPropNetPPPCommTerminalScript        , NULL             , NULL                           },
	{ "CommUseTerminalScript"     , NULL          , isBoolean        , &kSCPropNetPPPCommUseTerminalScript     , NULL             , NULL                           },

	// --- CCP: ---
	{ "CCPEnabled"                , NULL          , isBoolean        , &kSCPropNetPPPCCPEnabled                , NULL             , NULL                           },
	{ "CCPMPPE40Enabled"          , NULL          , isBoolean        , &kSCPropNetPPPCCPMPPE40Enabled          , NULL             , NULL                           },
	{ "CCPMPPE128Enabled"         , NULL          , isBoolean        , &kSCPropNetPPPCCPMPPE128Enabled         , NULL             , NULL                           },

	// --- IPCP: ---
	{ "IPCPCompressionVJ"         , NULL          , isBoolean        , &kSCPropNetPPPIPCPCompressionVJ         , NULL             , NULL                           },
	{ "IPCPUsePeerDNS"            , NULL          , isBoolean        , &kSCPropNetPPPIPCPUsePeerDNS            , NULL             , NULL                           },

	// --- LCP: ---
	{ "LCPEchoEnabled"            , NULL          , isBoolean        , &kSCPropNetPPPLCPEchoEnabled            , NULL             , NULL                           },
	{ "LCPEchoFailure"            , NULL          , isNumber         , &kSCPropNetPPPLCPEchoFailure            , NULL             , NULL                           },
	{ "LCPEchoInterval"           , NULL          , isNumber         , &kSCPropNetPPPLCPEchoInterval           , NULL             , NULL                           },
	{ "LCPCompressionACField"     , NULL          , isBoolean        , &kSCPropNetPPPLCPCompressionACField     , NULL             , NULL                           },
	{ "LCPCompressionPField"      , NULL          , isBoolean        , &kSCPropNetPPPLCPCompressionPField      , NULL             , NULL                           },
	{ "LCPMRU"                    , NULL          , isNumber         , &kSCPropNetPPPLCPMRU                    , NULL             , NULL                           },
	{ "LCPMTU"                    , NULL          , isNumber         , &kSCPropNetPPPLCPMTU                    , NULL             , NULL                           },
	{ "LCPReceiveACCM"            , NULL          , isNumber         , &kSCPropNetPPPLCPReceiveACCM            , NULL             , NULL                           },
	{ "LCPTransmitACCM"           , NULL          , isNumber         , &kSCPropNetPPPLCPTransmitACCM           , NULL             , NULL                           },

	// --- Help ---
	{ "?"                         , NULL          , isHelp           , NULL                                    , NULL             ,
	    "\nPPP configuration commands\n\n"
	    " set interface [Account account]\n"
	    " set interface [Password password]\n"
	    " set interface [Number telephone-number]\n"
	    " set interface [AlternateNumber telephone-number]\n"
	    " set interface [IdleReminder {enable|disable}]\n"
	    " set interface [IdleReminderTimer time-in-seconds]\n"
	    " set interface [DisconnectOnIdle {enable|disable}]\n"
	    " set interface [DisconnectOnIdleTimer time-in-seconds]\n"
	    " set interface [DisconnectOnLogout {enable|disable}]"
	}
};
#define	N_PPP_OPTIONS	(sizeof(pppOptions) / sizeof(pppOptions[0]))


static Boolean
set_interface_ppp(int argc, char **argv, CFMutableDictionaryRef newConfiguration)
{
	Boolean	ok;

	ok = _process_options(pppOptions, N_PPP_OPTIONS, argc, argv, newConfiguration);
	return ok;
}


/* -------------------- */


static Boolean
set_interface_vlan(int argc, char **argv, CFMutableDictionaryRef newConfiguration)
{
// xxxxx ("device", "tag")
SCPrint(TRUE, stdout, CFSTR("vlan interface management not yet supported\n"));
	return FALSE;
}


/* -------------------- */


__private_extern__
void
set_interface(int argc, char **argv)
{
	CFDictionaryRef		configuration;
	CFStringRef		interfaceType;
	CFMutableDictionaryRef	newConfiguration	= NULL;
	Boolean			ok			= FALSE;

	if (net_interface == NULL) {
		SCPrint(TRUE, stdout, CFSTR("interface not selected\n"));
		return;
	}

	if (argc < 1) {
		SCPrint(TRUE, stdout, CFSTR("set what?\n"));
		return;
	}

	configuration = SCNetworkInterfaceGetConfiguration(net_interface);
	if (configuration == NULL) {
		newConfiguration = CFDictionaryCreateMutable(NULL,
							     0,
							     &kCFTypeDictionaryKeyCallBacks,
							     &kCFTypeDictionaryValueCallBacks);
	} else {
		newConfiguration = CFDictionaryCreateMutableCopy(NULL, 0, configuration);
		CFDictionaryRemoveValue(newConfiguration, kSCResvInactive);
	}

	interfaceType = SCNetworkInterfaceGetInterfaceType(net_interface);

	if (CFEqual(interfaceType, kSCNetworkInterfaceTypeBond)) {
		ok = set_interface_bond(argc, argv, newConfiguration);
	} else if (CFEqual(interfaceType, kSCNetworkInterfaceTypeEthernet)) {
		ok = set_interface_ethernet(argc, argv, newConfiguration);
	} else if (CFEqual(interfaceType, kSCNetworkInterfaceTypeFireWire)) {
		ok = set_interface_firewire(argc, argv, newConfiguration);
	} else if (CFEqual(interfaceType, kSCNetworkInterfaceTypeModem)) {
		ok = set_interface_modem(argc, argv, newConfiguration);
	} else if (CFEqual(interfaceType, kSCNetworkInterfaceTypeIEEE80211)) {
		ok = set_interface_airport(argc, argv, newConfiguration);
	} else if (CFEqual(interfaceType, kSCNetworkInterfaceTypePPP)) {
		ok = set_interface_ppp(argc, argv, newConfiguration);
	} else if (CFEqual(interfaceType, kSCNetworkInterfaceTypeVLAN)) {
		ok = set_interface_vlan(argc, argv, newConfiguration);
	} else {
		SCPrint(TRUE, stdout, CFSTR("this interfaces configuration cannot be changed\n"));
	}

	if (!ok) {
		goto done;
	}

	if (((configuration == NULL) && (CFDictionaryGetCount(newConfiguration) > 0)) ||
	    ((configuration != NULL) && !CFEqual(configuration, newConfiguration))) {
		if (!SCNetworkInterfaceSetConfiguration(net_interface, newConfiguration)) {
			if (SCError() == kSCStatusNoKey) {
				SCPrint(TRUE, stdout, CFSTR("could not update per-service interface configuration\n"));
			} else {
				SCPrint(TRUE, stdout, CFSTR("%s\n"), SCErrorString(SCError()));
			}
			goto done;
		}

		net_changed = TRUE;
	}

    done :

	if (newConfiguration != NULL) CFRelease(newConfiguration);
	return;
}


/* -------------------- */


__private_extern__
void
show_interface(int argc, char **argv)
{
	SCNetworkInterfaceRef	interface;

	if (argc == 1) {
		interface = _find_interface(argv[0]);
	} else {
		if (net_interface != NULL) {
			interface = net_interface;
		} else {
			SCPrint(TRUE, stdout, CFSTR("interface not selected\n"));
			return;
		}
	}

	if (interface != NULL) {
		_show_interface(interface, CFSTR(""), TRUE);
	}

	return;
}


/* -------------------- */


__private_extern__
CFStringRef
_interface_description(SCNetworkInterfaceRef interface)
{
	CFMutableStringRef	description;
	CFStringRef		if_bsd_name;
	CFStringRef		if_type;

	description = CFStringCreateMutable(NULL, 0);

	if_type = SCNetworkInterfaceGetInterfaceType(interface);
	CFStringAppend(description, if_type);

	if_bsd_name = SCNetworkInterfaceGetBSDName(interface);
	if (if_bsd_name != NULL) {
		CFStringAppendFormat(description, NULL, CFSTR(" (%@)"), if_bsd_name);
	}

	interface = SCNetworkInterfaceGetInterface(interface);
	while ((interface != NULL) &&
	       !CFEqual(interface, kSCNetworkInterfaceIPv4)) {
		CFStringRef	childDescription;

		childDescription = _interface_description(interface);
		CFStringAppendFormat(description, NULL, CFSTR(" / %@"), childDescription);
		CFRelease(childDescription);

		interface = SCNetworkInterfaceGetInterface(interface);
	}

	return description;
}
