VPNService.c   [plain text]


/*
 * Copyright (c) 2012, 2013 Apple Inc. All rights reserved.
 */

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

static Boolean
isA_VPNService(CFTypeRef cf)
{
	if (isA_SCNetworkService(cf)) {
		SCNetworkInterfaceRef	interface = SCNetworkServiceGetInterface((SCNetworkServiceRef)cf);

		return (interface != NULL &&
			CFEqual(SCNetworkInterfaceGetInterfaceType(interface), kSCNetworkInterfaceTypeVPN));
	}

	return FALSE;
}


static CFArrayRef
copy_matching_services(SCPreferencesRef prefs, CFStringRef identifierDomain, CFStringRef identifier)
{
	CFMutableArrayRef	results		= NULL;
	CFArrayRef			services;

	services = SCNetworkServiceCopyAll(prefs);
	if (services != NULL) {
		CFIndex	idx;
		CFIndex	service_count = CFArrayGetCount(services);

		for (idx = 0; idx < service_count; idx++) {
			SCNetworkServiceRef		service = CFArrayGetValueAtIndex(services, idx);
			Boolean					matches	= FALSE;

			if (isA_VPNService(service)) {
				if (isA_CFString(identifierDomain) && isA_CFString(identifier)) {
					CFStringRef	ex_identifier = SCNetworkServiceCopyExternalID(service, identifierDomain);
					if (ex_identifier != NULL) {
						matches = CFEqual(ex_identifier, identifier);
						CFRelease(ex_identifier);
					}
				} else {
					matches = TRUE;
				}
			}

			if (matches) {
				if (results == NULL) {
					results = CFArrayCreateMutable(kCFAllocatorDefault, CFArrayGetCount(services) - idx, &kCFTypeArrayCallBacks);
				}
				CFArrayAppendValue(results, service);
			}
		}

		CFRelease(services);
	}

	return results;
}


static CFIndex
find_app_rule(CFDictionaryRef vpn_config, CFStringRef ruleIdentifier)
{
	CFArrayRef	app_rules = CFDictionaryGetValue(vpn_config, kSCPropNetVPNAppRules);
	CFIndex		idx;

	if (isA_CFArray(app_rules)) {
		CFIndex	rule_count = CFArrayGetCount(app_rules);

		for (idx = 0; idx < rule_count; idx++) {
			CFDictionaryRef	rule = CFArrayGetValueAtIndex(app_rules, idx);

			if (isA_CFDictionary(rule)) {
				CFStringRef	rule_id = CFDictionaryGetValue(rule, kSCValNetVPNAppRuleIdentifier);

				if (CFEqual(ruleIdentifier, rule_id)) {
					return idx;
				}
			}
		}
	}

	return -1;
}

static Boolean
validate_app_rule(CFDictionaryRef ruleSettings)
{
	CFIndex		account_count;
	CFArrayRef	accounts;
	Boolean		accounts_valid = FALSE;
	CFArrayRef	executables;
	Boolean		executables_valid = FALSE;
	CFIndex		executable_count;
	CFIndex		idx;
	CFArrayRef	match_domains;

	if (!isA_CFDictionary(ruleSettings)) {
		return FALSE;
	}

	/* Validate the executable array. It needs to have at least one value. */
	executables = CFDictionaryGetValue(ruleSettings, kSCValNetVPNAppRuleExecutableMatch);
	if (isA_CFArray(executables) && (executable_count = CFArrayGetCount(executables)) > 0) {
		executables_valid = TRUE;
		for (idx = 0; idx < executable_count; idx++) {
			CFDictionaryRef	executable	= CFArrayGetValueAtIndex(executables, idx);

			if (isA_CFDictionary(executable)) {
				CFStringRef		signingID	= CFDictionaryGetValue(executable, kSCValNetVPNAppRuleExecutableSigningIdentifier);
				CFStringRef		requirement	= CFDictionaryGetValue(executable, kSCValNetVPNAppRuleExecutableDesignatedRequirement);

				if (!isA_CFString(signingID) || CFStringGetLength(signingID) == 0) {
					executables_valid = FALSE;
					break;
				}

				if (requirement != NULL) {
					if (!isA_CFString(requirement) || CFStringGetLength(requirement) == 0) {
						executables_valid = FALSE;
						break;
					}
#if !TARGET_OS_IPHONE
				} else {
					executables_valid = FALSE;
					break;
#endif /* !TARGET_OS_IPHONE */
				}
			}
		}
	}

	/* Validate the accounts array. It needs to have at least one value. */
	accounts = CFDictionaryGetValue(ruleSettings, kSCValNetVPNAppRuleAccountIdentifierMatch);
	if (isA_CFArray(accounts) && (account_count = CFArrayGetCount(accounts)) > 0) {
		accounts_valid = TRUE;
		for (idx = 0; idx < account_count; idx++) {
			CFStringRef	account	= CFArrayGetValueAtIndex(accounts, idx);
			if (!isA_CFString(account)) {
				accounts_valid = FALSE;
				break;
			}
		}
	}

	/* Either executables or accounts must be present */
	if (!executables_valid && !accounts_valid) {
		return FALSE;
	}

	/* Validate the domains array. It's optional, so just make sure that it contains only strings if it's present. */
	match_domains = CFDictionaryGetValue(ruleSettings, kSCValNetVPNAppRuleDNSDomainMatch);
	if (match_domains != NULL) {
		CFIndex	match_domain_count;

		if (!isA_CFArray(match_domains)) {
			return FALSE;
		}

		match_domain_count = CFArrayGetCount(match_domains);
		for (idx = 0; idx < match_domain_count; idx++) {
			CFStringRef	domain	= CFArrayGetValueAtIndex(match_domains, idx);
			if (!isA_CFString(domain)) {
				return FALSE;
			}
		}
	}

	return TRUE;
}


CFArrayRef
VPNServiceCopyAllMatchingExternalID(SCPreferencesRef prefs, CFStringRef identifierDomain, CFStringRef identifier)
{
	CFArrayRef	services;

	if (prefs == NULL || !isA_CFString(identifierDomain) || !isA_CFString(identifier)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	services = copy_matching_services(prefs, identifierDomain, identifier);
	if (services == NULL) {
		_SCErrorSet(kSCStatusOK);
	}

	return services;
}


CFArrayRef
VPNServiceCopyAll(SCPreferencesRef prefs)
{
	CFArrayRef	services;

	if (prefs == NULL) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	services = copy_matching_services(prefs, NULL, NULL);
	if (services == NULL) {
		_SCErrorSet(kSCStatusOK);
	}

	return services;
}


CFArrayRef
VPNServiceCopyAppRuleIDs(VPNServiceRef service)
{
	CFMutableArrayRef		results		= NULL;
	CFDictionaryRef			vpn_config;

	if (!isA_VPNService(service)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	vpn_config = SCNetworkInterfaceGetConfiguration(SCNetworkServiceGetInterface(service));

	if (isA_CFDictionary(vpn_config)) {
		CFArrayRef	app_rules = CFDictionaryGetValue(vpn_config, kSCPropNetVPNAppRules);
		if (isA_CFArray(app_rules)) {
			CFIndex	app_rule_count = CFArrayGetCount(app_rules);
			CFIndex	idx;
			results = CFArrayCreateMutable(kCFAllocatorDefault, app_rule_count, &kCFTypeArrayCallBacks);
			for (idx = 0; idx < app_rule_count; idx++) {
				CFDictionaryRef	rule = CFArrayGetValueAtIndex(app_rules, idx);
				if (isA_CFDictionary(rule)) {
					CFStringRef	rule_ID = CFDictionaryGetValue(rule, kSCValNetVPNAppRuleIdentifier);
					if (isA_CFString(rule_ID)) {
						CFArrayAppendValue(results, CFDictionaryGetValue(rule, kSCValNetVPNAppRuleIdentifier));
					}
				}
			}
			if (CFArrayGetCount(results) == 0) {
				CFRelease(results);
				results = NULL;
			}
		}
	}

	if (results == NULL) {
		_SCErrorSet(kSCStatusOK);
	}

	return results;
}


Boolean
VPNServiceSetAppRule(VPNServiceRef service, CFStringRef ruleIdentifier, CFDictionaryRef ruleSettings)
{
	CFArrayRef				accounts;
	CFArrayRef				app_rules;
	CFArrayRef				executables;
	CFIndex					existing_idx		= -1;
	CFArrayRef				match_domains;
	CFMutableArrayRef		new_app_rules;
	CFMutableDictionaryRef	new_settings;
	CFMutableDictionaryRef	new_vpn_config;
	CFDictionaryRef			vpn_config;

	/* Basic parameter validation */

	if (!isA_VPNService(service) || !isA_CFString(ruleIdentifier)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return FALSE;
	}

	if (!validate_app_rule(ruleSettings)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return FALSE;
	}

	executables = CFDictionaryGetValue(ruleSettings, kSCValNetVPNAppRuleExecutableMatch);
	match_domains = CFDictionaryGetValue(ruleSettings, kSCValNetVPNAppRuleDNSDomainMatch);
	accounts = CFDictionaryGetValue(ruleSettings, kSCValNetVPNAppRuleAccountIdentifierMatch);

	/* Set the new rule config, replacing any existing rule */

	vpn_config = SCNetworkInterfaceGetConfiguration(SCNetworkServiceGetInterface(service));
	if (isA_CFDictionary(vpn_config)) {
		existing_idx = find_app_rule(vpn_config, ruleIdentifier);
		new_vpn_config = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, vpn_config);
	} else {
		new_vpn_config = CFDictionaryCreateMutable(kCFAllocatorDefault,
												   0,
												   &kCFTypeDictionaryKeyCallBacks,
												   &kCFTypeDictionaryValueCallBacks);
	}

	app_rules = CFDictionaryGetValue(new_vpn_config, kSCPropNetVPNAppRules);
	if (isA_CFArray(app_rules)) {
		new_app_rules = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, app_rules);
	} else {
		new_app_rules = CFArrayCreateMutable(kCFAllocatorDefault,
										     0,
										     &kCFTypeArrayCallBacks);
	}

	new_settings = CFDictionaryCreateMutable(kCFAllocatorDefault,
											 0,
											 &kCFTypeDictionaryKeyCallBacks,
											 &kCFTypeDictionaryValueCallBacks);

	CFDictionarySetValue(new_settings, kSCValNetVPNAppRuleIdentifier, ruleIdentifier);
	if (executables != NULL && CFArrayGetCount(executables) > 0) {
		CFDictionarySetValue(new_settings, kSCValNetVPNAppRuleExecutableMatch, executables);
	}
	if (match_domains != NULL && CFArrayGetCount(match_domains) > 0) {
		CFDictionarySetValue(new_settings, kSCValNetVPNAppRuleDNSDomainMatch, match_domains);
	}
	if (accounts != NULL && CFArrayGetCount(accounts) > 0) {
		CFDictionarySetValue(new_settings, kSCValNetVPNAppRuleAccountIdentifierMatch, accounts);
	}

	if (existing_idx >= 0) {
		CFArraySetValueAtIndex(new_app_rules, existing_idx, new_settings);
	} else {
		CFArrayAppendValue(new_app_rules, new_settings);
	}

	CFDictionarySetValue(new_vpn_config, kSCPropNetVPNAppRules, new_app_rules);

	SCNetworkInterfaceSetConfiguration(SCNetworkServiceGetInterface(service), new_vpn_config);

	CFRelease(new_vpn_config);
	CFRelease(new_app_rules);
	CFRelease(new_settings);

	return TRUE;
}


CFDictionaryRef
VPNServiceCopyAppRule(VPNServiceRef service, CFStringRef ruleIdentifier)
{
	CFDictionaryRef	vpn_config;

	if (!isA_VPNService(service) || !isA_CFString(ruleIdentifier)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return NULL;
	}

	vpn_config = SCNetworkInterfaceGetConfiguration(SCNetworkServiceGetInterface(service));
	if (isA_CFDictionary(vpn_config)) {
		CFIndex	idx = find_app_rule(vpn_config, ruleIdentifier);
		if (idx >= 0) {
			CFArrayRef		app_rules		= CFDictionaryGetValue(vpn_config, kSCPropNetVPNAppRules);
			CFDictionaryRef	ruleSettings	= CFArrayGetValueAtIndex(app_rules, idx);

			if (validate_app_rule(ruleSettings)) {
				return (CFDictionaryRef)CFRetain(ruleSettings);
			} else {
				_SCErrorSet(kSCStatusFailed);
			}
		} else {
			_SCErrorSet(kSCStatusNoKey);
		}
	} else {
		_SCErrorSet(kSCStatusFailed);
	}

	return NULL;
}


Boolean
VPNServiceRemoveAppRule(VPNServiceRef service, CFStringRef ruleIdentifier)
{
	CFDictionaryRef	vpn_config;

	if (!isA_VPNService(service) || !isA_CFString(ruleIdentifier)) {
		_SCErrorSet(kSCStatusInvalidArgument);
		return FALSE;
	}

	vpn_config = SCNetworkInterfaceGetConfiguration(SCNetworkServiceGetInterface(service));
	if (isA_CFDictionary(vpn_config)) {
		CFIndex	idx = find_app_rule(vpn_config, ruleIdentifier);
		if (idx >= 0) {
			CFArrayRef				current_app_rules;
			current_app_rules = CFDictionaryGetValue(vpn_config, kSCPropNetVPNAppRules);
			if (isA_CFArray(current_app_rules)) {
				CFMutableDictionaryRef	new_vpn_config;
				CFMutableArrayRef		new_app_rules;

				new_vpn_config = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, vpn_config);
				new_app_rules = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, current_app_rules);

				CFArrayRemoveValueAtIndex(new_app_rules, idx);
				if (CFArrayGetCount(new_app_rules) > 0) {
					CFDictionarySetValue(new_vpn_config, kSCPropNetVPNAppRules, new_app_rules);
				} else {
					CFDictionaryRemoveValue(new_vpn_config, kSCPropNetVPNAppRules);
				}

				SCNetworkInterfaceSetConfiguration(SCNetworkServiceGetInterface(service), new_vpn_config);

				CFRelease(new_vpn_config);
				CFRelease(new_app_rules);

				return TRUE;
			} else {
				_SCErrorSet(kSCStatusFailed);
			}
		} else {
			_SCErrorSet(kSCStatusNoKey);
		}
	} else {
		_SCErrorSet(kSCStatusFailed);
	}

	return FALSE;
}