1/*
2 * Copyright (c) 2013 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <SystemConfiguration/SystemConfiguration.h>
25#include <SystemConfiguration/SCPrivate.h>
26#include <arpa/inet.h>
27#include "scnc_main.h"
28#include "scnc_utils.h"
29#include "scnc_cache.h"
30
31#define kSCNCCacheFile CFSTR("com.apple.scnc-cache.plist")
32
33static SCPreferencesRef
34scnc_cache_get_prefs(void)
35{
36	static SCPreferencesRef prefs = NULL;
37	static dispatch_once_t predicate = 0;
38
39	dispatch_once(&predicate, ^{
40		prefs = SCPreferencesCreate(kCFAllocatorDefault, CFSTR("PPPController"), kSCNCCacheFile);
41		if (prefs == NULL) {
42			SCLog(TRUE, LOG_ERR, CFSTR("SCPreferencesCreate failed: %s"), SCErrorString(SCError()));
43		}
44
45		SCPreferencesSynchronize(prefs);
46	});
47
48	return prefs;
49}
50
51static CFDictionaryRef
52scnc_cache_get_routes (struct service *serv)
53{
54	SCPreferencesRef prefs = NULL;
55	prefs = scnc_cache_get_prefs();
56	if (prefs == NULL) {
57		return NULL;
58	}
59
60	return SCPreferencesGetValue(prefs, serv->serviceID);
61}
62
63/*
64 scnc_cache_update_key
65
66 This function should be used for modifying the cache. It merges the desired key (cacheKey) from an existing
67 dictionary (sourceDict) into the cache's mutable dictionary (cacheDict). If the value in the replacementDict is NULL
68 and addChildDictionaryIfNULL is not set, the value is removed from the cacheDict; if addChildDictionaryIfNULL is
69 set, then an empty dictionary will be added.
70
71 If a block is passed as the final argument, it is assumed that the cacheKey points to a subdictionary, and the
72 block will include further recursive calls to scnc_cache_update_key. If the block is NULL, then the entire value
73 for cacheKey is replaced wholesale.
74
75 If sourceKey is not NULL, then that key will be used to look up values in sourceDict. When the values are entered
76 into cacheDict, they will use cacheKey.
77*/
78static void
79scnc_cache_update_key (CFMutableDictionaryRef cacheDict, CFDictionaryRef sourceDict, Boolean addChildDictionaryIfNULL, CFStringRef cacheKey, CFStringRef sourceKey, void(^block)(CFMutableDictionaryRef, CFDictionaryRef))
80{
81	if (block) {
82		/* The type of the value being updated is a dictionary */
83		CFDictionaryRef oldCacheSubDict = CFDictionaryGetValue(cacheDict, cacheKey);
84		CFMutableDictionaryRef newCacheSubDict = NULL;
85		CFDictionaryRef sourceChildDict = NULL;
86		if (isA_CFDictionary(sourceDict)) {
87			sourceChildDict = CFDictionaryGetValue(sourceDict, sourceKey?sourceKey:cacheKey);
88		}
89
90		if (isA_CFDictionary(sourceChildDict) || addChildDictionaryIfNULL) {
91			if (isA_CFDictionary(oldCacheSubDict)) {
92				newCacheSubDict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, oldCacheSubDict);
93			} else {
94				newCacheSubDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
95			}
96
97			if (newCacheSubDict) {
98				block(newCacheSubDict, sourceChildDict);
99
100				if (CFDictionaryGetCount(newCacheSubDict) > 0) {
101					if (!my_CFEqual(oldCacheSubDict, newCacheSubDict)) {
102						CFDictionarySetValue(cacheDict, cacheKey, newCacheSubDict);
103					}
104				} else if (oldCacheSubDict) {
105					CFDictionaryRemoveValue(cacheDict, cacheKey);
106				}
107				CFRelease(newCacheSubDict);
108			}
109		} else if (oldCacheSubDict) {
110			/* Remove the key if it is not in the replacement dictionary */
111			CFDictionaryRemoveValue(cacheDict, cacheKey);
112		}
113	} else {
114		/* The type of the value being updated is ignored */
115		CFPropertyListRef sourceValue = NULL;
116		if (isA_CFDictionary(sourceDict)) {
117			sourceValue = CFDictionaryGetValue(sourceDict, sourceKey?sourceKey:cacheKey);
118		}
119
120		if (isA_CFPropertyList(sourceValue)) {
121			CFDictionarySetValue(cacheDict, cacheKey, sourceValue);
122		} else if (CFDictionaryContainsKey(cacheDict, cacheKey)) {
123			/* Remove the key if it is not in the replacement dictionary */
124			CFDictionaryRemoveValue(cacheDict, cacheKey);
125		}
126	}
127}
128
129/* Returns TRUE if updated the preferences file */
130Boolean
131scnc_cache_routing_table (struct service *serv, CFDictionaryRef serviceConfig, Boolean useOldKeys, Boolean doFullTunnel)
132{
133	SCPreferencesRef prefs = NULL;
134	CFDictionaryRef oldServiceDict = NULL;
135	CFMutableDictionaryRef newServiceDict = NULL;
136
137	prefs = scnc_cache_get_prefs();
138	if (prefs == NULL) {
139		return FALSE;
140	}
141
142	oldServiceDict = SCPreferencesGetValue(prefs, serv->serviceID);
143	if (isA_CFDictionary(oldServiceDict)) {
144		newServiceDict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, oldServiceDict);
145	} else {
146		newServiceDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
147	}
148
149	if (newServiceDict) {
150		scnc_cache_update_key(newServiceDict, serviceConfig, doFullTunnel, kSCNetworkConnectionNetworkInfoIPv4, useOldKeys?kSCEntNetIPv4:NULL, ^(CFMutableDictionaryRef newIPv4Dict, CFDictionaryRef ipv4Config) {
151			scnc_cache_update_key(newIPv4Dict, ipv4Config, doFullTunnel, kSCNetworkConnectionNetworkInfoIncludedRoutes, useOldKeys?kSCPropNetIPv4IncludedRoutes:NULL, ^(CFMutableDictionaryRef dict, CFDictionaryRef config) {
152				if (doFullTunnel) {
153					struct in_addr v4_zeros = {INADDR_ANY};
154					CFDataRef v4ZerosData = CFDataCreate(kCFAllocatorDefault, (uint8_t*)&v4_zeros, sizeof(struct in_addr));
155					CFMutableDictionaryRef tempDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
156					if (v4ZerosData && tempDict) {
157						CFDictionaryAddValue(tempDict, kSCNetworkConnectionNetworkInfoAddresses, v4ZerosData);
158						CFDictionaryAddValue(tempDict, kSCNetworkConnectionNetworkInfoMasks, v4ZerosData);
159					}
160					scnc_cache_update_key(dict, tempDict, FALSE, kSCNetworkConnectionNetworkInfoAddresses, NULL, NULL);
161					scnc_cache_update_key(dict, tempDict, FALSE, kSCNetworkConnectionNetworkInfoMasks, NULL, NULL);
162					my_CFRelease(&tempDict);
163					my_CFRelease(&v4ZerosData);
164				} else {
165					scnc_cache_update_key(dict, config, FALSE, kSCNetworkConnectionNetworkInfoAddresses, useOldKeys?kSCPropNetIPv4RouteDestinationAddress:NULL, NULL);
166					scnc_cache_update_key(dict, config, FALSE, kSCNetworkConnectionNetworkInfoMasks, useOldKeys?kSCPropNetIPv4RouteSubnetMask:NULL, NULL);
167				}
168			});
169			scnc_cache_update_key(newIPv4Dict, ipv4Config, FALSE, kSCNetworkConnectionNetworkInfoExcludedRoutes, useOldKeys?kSCPropNetIPv4ExcludedRoutes:NULL, ^(CFMutableDictionaryRef dict, CFDictionaryRef config) {
170				scnc_cache_update_key(dict, config, FALSE, kSCNetworkConnectionNetworkInfoAddresses, useOldKeys?kSCPropNetIPv4RouteDestinationAddress:NULL, NULL);
171				scnc_cache_update_key(dict, config, FALSE, kSCPropNetIPv4RouteSubnetMask, useOldKeys?kSCNetworkConnectionNetworkInfoMasks:NULL, NULL);
172			});
173		});
174
175		scnc_cache_update_key(newServiceDict, serviceConfig, doFullTunnel, kSCNetworkConnectionNetworkInfoIPv6, useOldKeys?kSCEntNetIPv6:NULL, ^(CFMutableDictionaryRef newIPv6Dict, CFDictionaryRef ipv6Config) {
176			scnc_cache_update_key(newIPv6Dict, ipv6Config, doFullTunnel, kSCNetworkConnectionNetworkInfoIncludedRoutes, useOldKeys?kSCPropNetIPv6IncludedRoutes:NULL, ^(CFMutableDictionaryRef dict, CFDictionaryRef config) {
177				if (doFullTunnel) {
178					struct in6_addr v6_zeros = IN6ADDR_ANY_INIT;
179					CFDataRef v6ZerosData = CFDataCreate(kCFAllocatorDefault, (uint8_t*)&v6_zeros, sizeof(struct in6_addr));
180					CFMutableDictionaryRef tempDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
181					if (v6ZerosData && tempDict) {
182						CFDictionaryAddValue(tempDict, kSCNetworkConnectionNetworkInfoAddresses, v6ZerosData);
183						CFDictionaryAddValue(tempDict, kSCNetworkConnectionNetworkInfoMasks, v6ZerosData);
184					}
185					scnc_cache_update_key(dict, tempDict, FALSE, kSCNetworkConnectionNetworkInfoAddresses, NULL, NULL);
186					scnc_cache_update_key(dict, tempDict, FALSE, kSCNetworkConnectionNetworkInfoMasks, NULL, NULL);
187					my_CFRelease(&tempDict);
188					my_CFRelease(&v6ZerosData);
189				} else {
190					scnc_cache_update_key(dict, config, FALSE, kSCNetworkConnectionNetworkInfoAddresses, useOldKeys?kSCPropNetIPv6RouteDestinationAddress:NULL, NULL);
191					scnc_cache_update_key(dict, config, FALSE, kSCNetworkConnectionNetworkInfoMasks, useOldKeys?kSCPropNetIPv6RoutePrefixLength:NULL, NULL);
192				}
193			});
194			scnc_cache_update_key(newIPv6Dict, ipv6Config, FALSE, kSCNetworkConnectionNetworkInfoExcludedRoutes, useOldKeys?kSCPropNetIPv6ExcludedRoutes:NULL, ^(CFMutableDictionaryRef dict, CFDictionaryRef config) {
195				scnc_cache_update_key(dict, config, FALSE, kSCNetworkConnectionNetworkInfoAddresses, useOldKeys?kSCPropNetIPv6RouteDestinationAddress:NULL, NULL);
196				scnc_cache_update_key(dict, config, FALSE, kSCNetworkConnectionNetworkInfoMasks, useOldKeys?kSCPropNetIPv6RoutePrefixLength:NULL, NULL);
197			});
198		});
199
200		if (!my_CFEqual(oldServiceDict, newServiceDict)) {
201			SCPreferencesSetValue(prefs, serv->serviceID, newServiceDict);
202			SCPreferencesCommitChanges(prefs);
203			SCPreferencesApplyChanges(prefs);
204
205			my_CFRelease(&serv->routeCache);
206			serv->routeCache = newServiceDict;
207			return TRUE;
208		}
209		CFRelease(newServiceDict);
210	}
211
212	return FALSE;
213}
214
215void
216scnc_cache_init_service (struct service *serv)
217{
218	CFDictionaryRef routeCache = scnc_cache_get_routes(serv);
219	my_CFRelease(&serv->routeCache);
220	serv->routeCache = my_CFRetain(routeCache);
221}
222
223void
224scnc_cache_flush_removed_services (CFArrayRef activeServices)
225{
226	SCPreferencesRef prefs = NULL;
227	CFArrayRef keys = NULL;
228	CFIndex numKeys = 0;
229	CFIndex numActiveServices = 0;
230	Boolean removed_values = FALSE;
231
232	prefs = scnc_cache_get_prefs();
233	keys = SCPreferencesCopyKeyList(prefs);
234	numKeys = CFArrayGetCount(keys);
235	numActiveServices = CFArrayGetCount(activeServices);
236
237	for (CFIndex i = 0; i < numKeys; i++) {
238		CFStringRef key = CFArrayGetValueAtIndex(keys, i);
239		if (!CFArrayContainsValue(activeServices, CFRangeMake(0, numActiveServices), CFArrayGetValueAtIndex(keys, i))) {
240			SCPreferencesRemoveValue(prefs, key);
241			removed_values = TRUE;
242		}
243	}
244
245	my_CFRelease(&keys);
246
247	if (removed_values) {
248		SCPreferencesCommitChanges(prefs);
249		SCPreferencesApplyChanges(prefs);
250	}
251}
252