#include <TargetConditionals.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCValidation.h>
#include <SystemConfiguration/SCPrivate.h>
#include <SystemConfiguration/VPNAppLayerPrivate.h>
#include <netdb.h>
#if !TARGET_OS_SIMULATOR
#include <ne_session.h>
#endif // !TARGET_OS_SIMULATOR
CFStringRef
SCDynamicStoreKeyCreateProxies(CFAllocatorRef allocator)
{
return SCDynamicStoreKeyCreateNetworkGlobalEntity(allocator,
kSCDynamicStoreDomainState,
kSCEntNetProxies);
}
static void
validate_proxy_content(CFMutableDictionaryRef proxies,
CFStringRef proxy_enable,
CFStringRef proxy_host,
CFStringRef proxy_port,
const char * proxy_service,
int proxy_defaultport)
{
int enabled = 0;
CFNumberRef num;
num = CFDictionaryGetValue(proxies, proxy_enable);
if (num != NULL) {
if (!isA_CFNumber(num) ||
!CFNumberGetValue(num, kCFNumberIntType, &enabled)) {
goto disable; }
}
if (proxy_host != NULL) {
CFStringRef host;
host = CFDictionaryGetValue(proxies, proxy_host);
if ((enabled == 0) && (host != NULL)) {
goto disable; }
if ((enabled != 0) && !isA_CFString(host)) {
goto disable; }
}
if (proxy_port != NULL) {
CFNumberRef port;
int s_port = 0;
port = CFDictionaryGetValue(proxies, proxy_port);
if ((enabled == 0) && (port != NULL)) {
goto disable; }
if ((enabled != 0) && (port != NULL)) {
if (!isA_CFNumber(port) ||
!CFNumberGetValue(port, kCFNumberIntType, &s_port) ||
(s_port > UINT16_MAX)) {
goto disable; }
if (s_port == 0) {
port = NULL; }
}
if ((enabled != 0) && (port == NULL)) {
struct servent *service;
service = getservbyname(proxy_service, "tcp");
if (service != NULL) {
s_port = ntohs(service->s_port);
} else {
s_port = proxy_defaultport;
}
num = CFNumberCreate(NULL, kCFNumberIntType, &s_port);
CFDictionarySetValue(proxies, proxy_port, num);
CFRelease(num);
}
}
return;
disable :
enabled = 0;
num = CFNumberCreate(NULL, kCFNumberIntType, &enabled);
CFDictionarySetValue(proxies, proxy_enable, num);
CFRelease(num);
if (proxy_host != NULL) {
CFDictionaryRemoveValue(proxies, proxy_host);
}
if (proxy_port != NULL) {
CFDictionaryRemoveValue(proxies, proxy_port);
}
return;
}
static void
normalize_scoped_proxy(const void *key, const void *value, void *context);
static void
normalize_services_proxy(const void *key, const void *value, void *context);
static void
normalize_supplemental_proxy(const void *value, void *context);
static CF_RETURNS_RETAINED CFDictionaryRef
__SCNetworkProxiesCopyNormalized(CFDictionaryRef proxy)
{
CFArrayRef array;
CFMutableDictionaryRef newProxy;
CFNumberRef num;
CFDictionaryRef scoped;
CFDictionaryRef services;
CFArrayRef supplemental;
if (!isA_CFDictionary(proxy)) {
proxy = CFDictionaryCreate(NULL,
NULL,
NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
return proxy;
}
newProxy = CFDictionaryCreateMutableCopy(NULL, 0, proxy);
validate_proxy_content(newProxy,
kSCPropNetProxiesFTPEnable,
kSCPropNetProxiesFTPProxy,
kSCPropNetProxiesFTPPort,
"ftp",
21);
validate_proxy_content(newProxy,
kSCPropNetProxiesGopherEnable,
kSCPropNetProxiesGopherProxy,
kSCPropNetProxiesGopherPort,
"gopher",
70);
validate_proxy_content(newProxy,
kSCPropNetProxiesHTTPEnable,
kSCPropNetProxiesHTTPProxy,
kSCPropNetProxiesHTTPPort,
"http",
80);
validate_proxy_content(newProxy,
kSCPropNetProxiesHTTPSEnable,
kSCPropNetProxiesHTTPSProxy,
kSCPropNetProxiesHTTPSPort,
"https",
443);
validate_proxy_content(newProxy,
kSCPropNetProxiesRTSPEnable,
kSCPropNetProxiesRTSPProxy,
kSCPropNetProxiesRTSPPort,
"rtsp",
554);
validate_proxy_content(newProxy,
kSCPropNetProxiesSOCKSEnable,
kSCPropNetProxiesSOCKSProxy,
kSCPropNetProxiesSOCKSPort,
"socks",
1080);
if (CFDictionaryContainsKey(newProxy, kSCPropNetProxiesProxyAutoConfigURLString)) {
validate_proxy_content(newProxy,
kSCPropNetProxiesProxyAutoConfigEnable,
kSCPropNetProxiesProxyAutoConfigURLString,
NULL,
NULL,
0);
CFDictionaryRemoveValue(newProxy, kSCPropNetProxiesProxyAutoConfigJavaScript);
} else {
validate_proxy_content(newProxy,
kSCPropNetProxiesProxyAutoConfigEnable,
kSCPropNetProxiesProxyAutoConfigJavaScript,
NULL,
NULL,
0);
}
validate_proxy_content(newProxy,
kSCPropNetProxiesProxyAutoDiscoveryEnable,
NULL,
NULL,
NULL,
0);
validate_proxy_content(newProxy,
kSCPropNetProxiesFallBackAllowed,
NULL,
NULL,
NULL,
0);
num = CFDictionaryGetValue(newProxy, kSCPropNetProxiesFTPPassive);
if (num != NULL) {
int enabled = 0;
if (!isA_CFNumber(num) ||
!CFNumberGetValue(num, kCFNumberIntType, &enabled)) {
enabled = 1;
num = CFNumberCreate(NULL, kCFNumberIntType, &enabled);
CFDictionarySetValue(newProxy,
kSCPropNetProxiesFTPPassive,
num);
CFRelease(num);
}
}
array = CFDictionaryGetValue(newProxy, kSCPropNetProxiesExceptionsList);
if (array != NULL) {
CFIndex i;
CFIndex n;
n = isA_CFArray(array) ? CFArrayGetCount(array) : 0;
for (i = 0; i < n; i++) {
CFStringRef str;
str = CFArrayGetValueAtIndex(array, i);
if (!isA_CFString(str)) {
n = 0;
break;
}
}
if (n == 0) {
CFDictionaryRemoveValue(newProxy, kSCPropNetProxiesExceptionsList);
}
}
num = CFDictionaryGetValue(newProxy, kSCPropNetProxiesExcludeSimpleHostnames);
if (num != NULL) {
int enabled;
if (!isA_CFNumber(num) ||
!CFNumberGetValue(num, kCFNumberIntType, &enabled)) {
enabled = 0;
num = CFNumberCreate(NULL, kCFNumberIntType, &enabled);
CFDictionarySetValue(newProxy,
kSCPropNetProxiesExcludeSimpleHostnames,
num);
CFRelease(num);
}
}
scoped = CFDictionaryGetValue(newProxy, kSCPropNetProxiesScoped);
if (isA_CFDictionary(scoped)) {
CFMutableDictionaryRef newScoped;
newScoped = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryApplyFunction(scoped,
normalize_scoped_proxy,
newScoped);
CFDictionarySetValue(newProxy, kSCPropNetProxiesScoped, newScoped);
CFRelease(newScoped);
}
services = CFDictionaryGetValue(newProxy, kSCPropNetProxiesServices);
if (isA_CFDictionary(services)) {
CFMutableDictionaryRef newServices;
newServices = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryApplyFunction(services,
normalize_services_proxy,
newServices);
CFDictionarySetValue(newProxy, kSCPropNetProxiesServices, newServices);
CFRelease(newServices);
}
supplemental = CFDictionaryGetValue(newProxy, kSCPropNetProxiesSupplemental);
if (isA_CFArray(supplemental)) {
CFMutableArrayRef newSupplemental;
newSupplemental = CFArrayCreateMutable(NULL,
0,
&kCFTypeArrayCallBacks);
CFArrayApplyFunction(supplemental,
CFRangeMake(0, CFArrayGetCount(supplemental)),
normalize_supplemental_proxy,
newSupplemental);
CFDictionarySetValue(newProxy, kSCPropNetProxiesSupplemental, newSupplemental);
CFRelease(newSupplemental);
}
proxy = CFDictionaryCreateCopy(NULL,newProxy);
CFRelease(newProxy);
return proxy;
}
static void
normalize_scoped_proxy(const void *key, const void *value, void *context)
{
CFStringRef interface = (CFStringRef)key;
CFDictionaryRef proxy = (CFDictionaryRef)value;
CFMutableDictionaryRef newScoped = (CFMutableDictionaryRef)context;
proxy = __SCNetworkProxiesCopyNormalized(proxy);
CFDictionarySetValue(newScoped, interface, proxy);
CFRelease(proxy);
return;
}
static void
normalize_services_proxy(const void *key, const void *value, void *context)
{
CFStringRef serviceID = (CFStringRef)key;
CFDictionaryRef proxy = (CFDictionaryRef)value;
CFMutableDictionaryRef newServices = (CFMutableDictionaryRef)context;
proxy = __SCNetworkProxiesCopyNormalized(proxy);
CFDictionarySetValue(newServices, serviceID, proxy);
CFRelease(proxy);
return;
}
static void
normalize_supplemental_proxy(const void *value, void *context)
{
CFDictionaryRef proxy = (CFDictionaryRef)value;
CFMutableArrayRef newSupplemental = (CFMutableArrayRef)context;
proxy = __SCNetworkProxiesCopyNormalized(proxy);
CFArrayAppendValue(newSupplemental, proxy);
CFRelease(proxy);
return;
}
CFDictionaryRef
SCDynamicStoreCopyProxies(SCDynamicStoreRef store)
{
return SCDynamicStoreCopyProxiesWithOptions(store, NULL);
}
const CFStringRef kSCProxiesNoGlobal = CFSTR("NO_GLOBAL");
CFDictionaryRef
SCDynamicStoreCopyProxiesWithOptions(SCDynamicStoreRef store, CFDictionaryRef options)
{
Boolean bypass = FALSE;
CFStringRef key;
CFDictionaryRef proxies = NULL;
if (options != NULL) {
CFBooleanRef bypassGlobalOption;
if (!isA_CFDictionary(options)) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
bypassGlobalOption = CFDictionaryGetValue(options, kSCProxiesNoGlobal);
if (isA_CFBoolean(bypassGlobalOption) && CFBooleanGetValue(bypassGlobalOption)) {
bypass = TRUE;
}
}
key = SCDynamicStoreKeyCreateProxies(NULL);
proxies = SCDynamicStoreCopyValue(store, key);
CFRelease(key);
if (isA_CFDictionary(proxies) &&
CFDictionaryContainsKey(proxies, kSCPropNetProxiesBypassAllowed)) {
CFMutableDictionaryRef newProxies;
newProxies = CFDictionaryCreateMutableCopy(NULL, 0, proxies);
CFRelease(proxies);
CFDictionaryRemoveValue(newProxies, kSCPropNetProxiesBypassAllowed);
proxies = newProxies;
}
if (proxies != NULL) {
CFDictionaryRef base = proxies;
proxies = __SCNetworkProxiesCopyNormalized(base);
CFRelease(base);
} else {
proxies = CFDictionaryCreate(NULL,
NULL,
NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
}
return proxies;
}
static CFArrayRef
_SCNetworkProxiesCopyMatchingInternal(CFDictionaryRef globalConfiguration,
CFStringRef server,
CFStringRef interface,
CFDictionaryRef options)
{
CFMutableDictionaryRef newProxy;
uuid_t match_uuid;
CFArrayRef proxies = NULL;
CFDictionaryRef proxy;
int sc_status = kSCStatusOK;
CFStringRef trimmed = NULL;
if (!isA_CFDictionary(globalConfiguration)) {
_SCErrorSet(kSCStatusOK);
return NULL;
}
uuid_clear(match_uuid);
if (isA_CFDictionary(options)) {
CFUUIDRef euuid;
interface = CFDictionaryGetValue(options, kSCProxiesMatchInterface);
interface = isA_CFString(interface);
server = CFDictionaryGetValue(options, kSCProxiesMatchServer);
server = isA_CFString(server);
euuid = CFDictionaryGetValue(options, kSCProxiesMatchExecutableUUID);
euuid = isA_CFType(euuid, CFUUIDGetTypeID());
if (euuid != NULL) {
CFUUIDBytes uuid_bytes = CFUUIDGetUUIDBytes(euuid);
uuid_copy(match_uuid, (const uint8_t *)&uuid_bytes);
}
}
if (interface != NULL) {
CFDictionaryRef scoped;
if (!isA_CFString(interface) ||
(CFStringGetLength(interface) == 0)) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
scoped = CFDictionaryGetValue(globalConfiguration, kSCPropNetProxiesScoped);
if (scoped == NULL) {
#if !TARGET_OS_SIMULATOR
if (CFDictionaryContainsKey(globalConfiguration, kSCPropNetProxiesBypassAllowed) &&
ne_session_always_on_vpn_configs_present()) {
goto useDefault;
}
#endif // !TARGET_OS_SIMULATOR
_SCErrorSet(kSCStatusOK);
return NULL;
}
if (!isA_CFDictionary(scoped)) {
_SCErrorSet(kSCStatusFailed);
return NULL;
}
proxy = CFDictionaryGetValue(scoped, interface);
if (proxy == NULL) {
_SCErrorSet(kSCStatusOK);
return NULL;
}
if (!isA_CFDictionary(proxy)) {
_SCErrorSet(kSCStatusFailed);
return NULL;
}
proxies = CFArrayCreate(NULL, (const void **)&proxy, 1, &kCFTypeArrayCallBacks);
return proxies;
}
if (server != NULL) {
CFIndex i;
CFMutableArrayRef matching = NULL;
CFIndex n = 0;
CFIndex server_len;
CFArrayRef supplemental;
trimmed = _SC_trimDomain(server);
if (trimmed == NULL) {
_SCErrorSet(kSCStatusInvalidArgument);
return NULL;
}
server = trimmed;
server_len = CFStringGetLength(server);
supplemental = CFDictionaryGetValue(globalConfiguration, kSCPropNetProxiesSupplemental);
if (supplemental != NULL) {
if (!isA_CFArray(supplemental)) {
sc_status = kSCStatusFailed;
goto done;
}
n = CFArrayGetCount(supplemental);
}
for (i = 0; i < n; i++) {
CFStringRef domain;
CFIndex domain_len;
CFIndex n_matching;
proxy = CFArrayGetValueAtIndex(supplemental, i);
if (!isA_CFDictionary(proxy)) {
continue;
}
domain = CFDictionaryGetValue(proxy, kSCPropNetProxiesSupplementalMatchDomain);
if (!isA_CFString(domain)) {
continue;
}
domain_len = CFStringGetLength(domain);
if (domain_len > 0) {
if (!CFStringFindWithOptions(server,
domain,
CFRangeMake(0, server_len),
kCFCompareCaseInsensitive|kCFCompareAnchored|kCFCompareBackwards,
NULL)) {
continue;
}
if ((server_len > domain_len) &&
!CFStringFindWithOptions(server,
CFSTR("."),
CFRangeMake(0, server_len - domain_len),
kCFCompareCaseInsensitive|kCFCompareAnchored|kCFCompareBackwards,
NULL)) {
continue;
}
}
if (matching == NULL) {
matching = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
}
n_matching = CFArrayGetCount(matching);
newProxy = CFDictionaryCreateMutableCopy(NULL, 0, proxy);
CFDictionaryRemoveValue(newProxy, kSCPropNetProxiesSupplementalMatchDomain);
if ((n_matching == 0) ||
!CFArrayContainsValue(matching, CFRangeMake(0, n_matching), newProxy)) {
CFArrayAppendValue(matching, newProxy);
}
CFRelease(newProxy);
}
if (matching != NULL) {
proxies = CFArrayCreateCopy(NULL, matching);
CFRelease(matching);
goto done;
}
}
#if !TARGET_OS_SIMULATOR
useDefault :
#endif // !TARGET_OS_SIMULATOR
newProxy = CFDictionaryCreateMutableCopy(NULL, 0, globalConfiguration);
CFDictionaryRemoveValue(newProxy, kSCPropNetProxiesScoped);
CFDictionaryRemoveValue(newProxy, kSCPropNetProxiesServices);
CFDictionaryRemoveValue(newProxy, kSCPropNetProxiesSupplemental);
proxies = CFArrayCreate(NULL, (const void **)&newProxy, 1, &kCFTypeArrayCallBacks);
CFRelease(newProxy);
done :
if (sc_status != kSCStatusOK) {
if (proxies != NULL) {
CFRelease(proxies);
proxies = NULL;
}
_SCErrorSet(sc_status);
}
if (trimmed != NULL) CFRelease(trimmed);
return proxies;
}
CFArrayRef
SCNetworkProxiesCopyMatching(CFDictionaryRef globalConfiguration,
CFStringRef server,
CFStringRef interface)
{
return _SCNetworkProxiesCopyMatchingInternal(globalConfiguration, server, interface, NULL);
}
CFArrayRef
SCNetworkProxiesCopyMatchingWithOptions(CFDictionaryRef globalConfiguration,
CFDictionaryRef options)
{
return _SCNetworkProxiesCopyMatchingInternal(globalConfiguration, NULL, NULL, options);
}