#include "SCNetworkConfigurationInternal.h"
#include "dy_framework.h"
static CFStringRef g_apple_app_prefix = CFSTR("com.apple.");
static struct {
CFStringRef signing_id;
Boolean domains_required;
} g_apple_app_exceptions[] = {
#if TARGET_OS_IPHONE
{ CFSTR("com.apple.mobilesafari"), TRUE },
{ CFSTR("com.apple.webapp"), TRUE },
#else
{ CFSTR("com.apple.WebKit.NetworkProcess"), TRUE },
#endif
};
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, Boolean check_for_apple_apps)
{
CFIndex account_count = 0;
CFArrayRef accounts;
CFIndex exception_idx = -1;
CFArrayRef executables;
CFIndex executable_count = 0;
Boolean found_exception = FALSE;
CFIndex idx;
CFArrayRef match_domains;
CFIndex match_domain_count = 0;
if (!isA_CFDictionary(ruleSettings)) {
return FALSE;
}
executables = CFDictionaryGetValue(ruleSettings, kSCValNetVPNAppRuleExecutableMatch);
if (isA_CFArray(executables) && (executable_count = CFArrayGetCount(executables)) > 0) {
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) {
return FALSE;
}
if (check_for_apple_apps && CFStringHasPrefix(signingID, g_apple_app_prefix)) {
for (exception_idx = 0;
exception_idx < (CFIndex)(sizeof(g_apple_app_exceptions) / sizeof(g_apple_app_exceptions[0]));
exception_idx++)
{
if (CFStringCompare(signingID, g_apple_app_exceptions[exception_idx].signing_id, 0) == 0) {
found_exception = TRUE;
break;
}
}
if (!found_exception) {
Boolean can_set_apple_app_rules = FALSE;
SecTaskRef current_task = SecTaskCreateFromSelf(kCFAllocatorDefault);
if (current_task != NULL) {
CFBooleanRef entitlement =
SecTaskCopyValueForEntitlement(current_task,
CFSTR("com.apple.private.app-vpn-config"),
NULL);
can_set_apple_app_rules = (isA_CFBoolean(entitlement) && CFBooleanGetValue(entitlement));
if (entitlement != NULL) {
CFRelease(entitlement);
}
CFRelease(current_task);
}
if (!can_set_apple_app_rules) {
return FALSE;
}
}
}
if (requirement != NULL) {
if (!isA_CFString(requirement) || CFStringGetLength(requirement) == 0) {
return FALSE;
}
#if !TARGET_OS_IPHONE
} else {
return FALSE;
#endif
}
}
}
}
accounts = CFDictionaryGetValue(ruleSettings, kSCValNetVPNAppRuleAccountIdentifierMatch);
if (isA_CFArray(accounts) && (account_count = CFArrayGetCount(accounts)) > 0) {
for (idx = 0; idx < account_count; idx++) {
CFStringRef account = CFArrayGetValueAtIndex(accounts, idx);
if (!isA_CFString(account)) {
return FALSE;
}
}
}
if (executable_count == 0 && account_count == 0) {
return FALSE;
}
match_domains = CFDictionaryGetValue(ruleSettings, kSCValNetVPNAppRuleDNSDomainMatch);
if (match_domains != NULL) {
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;
}
}
}
if (match_domain_count == 0 &&
found_exception &&
exception_idx >= 0 &&
g_apple_app_exceptions[exception_idx].domains_required)
{
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)
{
SCNetworkInterfaceRef interface;
CFMutableArrayRef results = NULL;
CFDictionaryRef vpn_config;
if (!isA_VPNService(service)) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
interface = SCNetworkServiceGetInterface(service);
if (interface == NULL) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
vpn_config = SCNetworkInterfaceGetConfiguration(interface);
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;
SCNetworkInterfaceRef interface;
CFArrayRef match_domains;
CFMutableArrayRef new_app_rules;
CFMutableDictionaryRef new_settings;
CFMutableDictionaryRef new_vpn_config;
CFDictionaryRef vpn_config;
if (!isA_VPNService(service) || !isA_CFString(ruleIdentifier)) {
_SCErrorSet(kSCStatusInvalidArgument);
return FALSE;
}
if (!validate_app_rule(ruleSettings, TRUE)) {
_SCErrorSet(kSCStatusInvalidArgument);
return FALSE;
}
interface = SCNetworkServiceGetInterface(service);
if (interface == NULL) {
_SCErrorSet(kSCStatusInvalidArgument);
return FALSE;
}
executables = CFDictionaryGetValue(ruleSettings, kSCValNetVPNAppRuleExecutableMatch);
match_domains = CFDictionaryGetValue(ruleSettings, kSCValNetVPNAppRuleDNSDomainMatch);
accounts = CFDictionaryGetValue(ruleSettings, kSCValNetVPNAppRuleAccountIdentifierMatch);
vpn_config = SCNetworkInterfaceGetConfiguration(interface);
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(interface, new_vpn_config);
CFRelease(new_vpn_config);
CFRelease(new_app_rules);
CFRelease(new_settings);
return TRUE;
}
CFDictionaryRef
VPNServiceCopyAppRule(VPNServiceRef service, CFStringRef ruleIdentifier)
{
SCNetworkInterfaceRef interface;
CFDictionaryRef vpn_config;
if (!isA_VPNService(service) || !isA_CFString(ruleIdentifier)) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
interface = SCNetworkServiceGetInterface(service);
if (interface == NULL) {
_SCErrorSet(kSCStatusInvalidArgument);
return FALSE;
}
vpn_config = SCNetworkInterfaceGetConfiguration(interface);
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, FALSE)) {
return (CFDictionaryRef)CFRetain(ruleSettings);
} else {
_SCErrorSet(kSCStatusFailed);
}
} else {
_SCErrorSet(kSCStatusNoKey);
}
} else {
_SCErrorSet(kSCStatusFailed);
}
return NULL;
}
Boolean
VPNServiceRemoveAppRule(VPNServiceRef service, CFStringRef ruleIdentifier)
{
SCNetworkInterfaceRef interface;
CFDictionaryRef vpn_config;
if (!isA_VPNService(service) || !isA_CFString(ruleIdentifier)) {
_SCErrorSet(kSCStatusInvalidArgument);
return FALSE;
}
interface = SCNetworkServiceGetInterface(service);
if (interface == NULL) {
_SCErrorSet(kSCStatusInvalidArgument);
return FALSE;
}
vpn_config = SCNetworkInterfaceGetConfiguration(interface);
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(interface, new_vpn_config);
CFRelease(new_vpn_config);
CFRelease(new_app_rules);
return TRUE;
} else {
_SCErrorSet(kSCStatusFailed);
}
} else {
_SCErrorSet(kSCStatusNoKey);
}
} else {
_SCErrorSet(kSCStatusFailed);
}
return FALSE;
}
Boolean
VPNServiceIsManagedAppVPN(VPNServiceRef service)
{
Boolean result = FALSE;
CFStringRef mc_external_id = SCNetworkServiceCopyExternalID(service, CFSTR("MCVPNUUID"));
if (isA_CFString(mc_external_id)) {
result = TRUE;
}
if (mc_external_id != NULL) {
CFRelease(mc_external_id);
}
return result;
}