1/*
2 * Copyright (c) 2000-2006, 2008, 2011, 2012 Apple 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/*
25 * Modification History
26 *
27 * June 1, 2001			Allan Nathanson <ajn@apple.com>
28 * - public API conversion
29 *
30 * November 16, 2000		Allan Nathanson <ajn@apple.com>
31 * - initial revision
32 */
33
34#include <SystemConfiguration/SystemConfiguration.h>
35#include <SystemConfiguration/SCValidation.h>
36#include <SystemConfiguration/SCPrivate.h>
37#include "SCPreferencesInternal.h"
38
39#define	MAXLINKS	8
40
41static CF_RETURNS_RETAINED CFMutableArrayRef
42normalizePath(CFStringRef path)
43{
44	CFMutableArrayRef	elements;
45	CFIndex			n;
46	CFArrayRef		tmpElements;
47
48	if (!isA_CFString(path)) {
49		_SCErrorSet(kSCStatusInvalidArgument);
50		return NULL;
51	}
52
53	if (!CFStringHasPrefix(path, CFSTR("/"))) {
54		/* if no root separator */
55		return NULL;
56	}
57
58	tmpElements = CFStringCreateArrayBySeparatingStrings(NULL, path, CFSTR("/"));
59	elements    = CFArrayCreateMutableCopy(NULL, 0, tmpElements);
60	CFRelease(tmpElements);
61
62	/* remove empty path components */
63	n = CFArrayGetCount(elements);
64	while (n-- > 0) {
65		CFStringRef	pathElement;
66
67		pathElement = CFArrayGetValueAtIndex(elements, n);
68		if (CFStringGetLength(pathElement) == 0) {
69			CFArrayRemoveValueAtIndex(elements, n);
70		}
71	}
72
73	return elements;
74}
75
76
77static Boolean
78getPath(SCPreferencesRef prefs, CFStringRef path, CFDictionaryRef *entity)
79{
80	CFStringRef		element;
81	CFMutableArrayRef	elements;
82	CFIndex			i;
83	CFStringRef		link;
84	CFIndex			nElements;
85	CFIndex			nLinks		= 0;
86	Boolean			ok		= FALSE;
87	CFDictionaryRef		value		= NULL;
88
89	elements = normalizePath(path);
90	if (elements == NULL) {
91		_SCErrorSet(kSCStatusNoKey);
92		return FALSE;
93	}
94
95    restart :
96
97	nElements = CFArrayGetCount(elements);
98
99	if (nElements < 1) {
100		SCPreferencesPrivateRef	prefsPrivate	= (SCPreferencesPrivateRef)prefs;
101
102		__SCPreferencesAccess(prefs);
103
104		*entity = prefsPrivate->prefs;
105		ok = TRUE;
106		goto done;
107	}
108
109	for (i = 0; i < nElements; i++) {
110		element = CFArrayGetValueAtIndex(elements, i);
111		if (i == 0) {
112			value = SCPreferencesGetValue(prefs, CFArrayGetValueAtIndex(elements, 0));
113		} else {
114			value = CFDictionaryGetValue(value, element);
115		}
116		if (value == NULL) {
117			/* if path component does not exist */
118			_SCErrorSet(kSCStatusNoKey);
119			goto done;
120		}
121
122		if (!isA_CFDictionary(value)) {
123			/* if path component not a dictionary */
124			_SCErrorSet(kSCStatusNoKey);
125			goto done;
126		}
127
128		if ((i < nElements - 1) &&
129		    CFDictionaryGetValueIfPresent(value, kSCResvLink, (const void **)&link)) {
130			/*
131			 * if not the last path component and this
132			 * element is a link
133			 */
134			CFMutableArrayRef	linkElements;
135
136			if (++nLinks > MAXLINKS) {
137				/* if we are chasing our tail */
138				_SCErrorSet(kSCStatusMaxLink);
139				goto done;
140			}
141
142			linkElements = normalizePath(link);
143			if (linkElements == NULL) {
144				/* if the link is bad */
145				_SCErrorSet(kSCStatusNoKey);
146				goto done;
147			}
148
149			CFArrayAppendArray(linkElements,
150					   elements,
151					   CFRangeMake(i + 1, nElements-i - 1));
152			CFRelease(elements);
153			elements = linkElements;
154
155			goto restart;
156		}
157	}
158
159	*entity = value;
160	ok = TRUE;
161
162    done :
163
164	CFRelease(elements);
165	return ok;
166}
167
168
169static Boolean
170setPath(SCPreferencesRef prefs, CFStringRef path, CFDictionaryRef entity)
171{
172	CFStringRef		element;
173	CFMutableArrayRef	elements;
174	CFIndex			i;
175	CFStringRef		link;
176	CFIndex			nElements;
177	CFIndex			nLinks		= 0;
178	CFDictionaryRef		newEntity	= NULL;
179	CFDictionaryRef		node		= NULL;
180	CFMutableArrayRef	nodes		= NULL;
181	Boolean			ok		= FALSE;
182
183	if ((entity != NULL) && !isA_CFDictionary(entity)) {
184		_SCErrorSet(kSCStatusInvalidArgument);
185		return FALSE;
186	}
187
188	elements = normalizePath(path);
189	if (elements == NULL) {
190		_SCErrorSet(kSCStatusNoKey);
191		return FALSE;
192	}
193
194    restart :
195
196	nElements = CFArrayGetCount(elements);
197
198	if (nElements < 1) {
199		SCPreferencesPrivateRef	prefsPrivate	= (SCPreferencesPrivateRef)prefs;
200
201		__SCPreferencesAccess(prefs);
202
203		if (prefsPrivate->prefs != NULL) {
204			CFRelease(prefsPrivate->prefs);
205		}
206
207		if (entity == NULL) {
208			prefsPrivate->prefs = CFDictionaryCreateMutable(NULL,
209									0,
210									&kCFTypeDictionaryKeyCallBacks,
211									&kCFTypeDictionaryValueCallBacks);
212		} else {
213			prefsPrivate->prefs = CFDictionaryCreateMutableCopy(NULL, 0, entity);
214		}
215
216		prefsPrivate->changed = TRUE;
217		ok = TRUE;
218		goto done;
219	}
220
221	nodes = CFArrayCreateMutable(NULL, nElements - 1, &kCFTypeArrayCallBacks);
222	for (i = 0; i < nElements - 1; i++) {
223		element = CFArrayGetValueAtIndex(elements, i);
224		if (i == 0) {
225			node = SCPreferencesGetValue(prefs, element);
226		} else {
227			node = CFDictionaryGetValue(node, element);
228
229		}
230
231		if (node) {
232			/* if path component exists */
233			CFArrayAppendValue(nodes, node);
234		} else {
235			/* if path component does not exist */
236			node = CFDictionaryCreate(NULL,
237						  NULL,
238						  NULL,
239						  0,
240						  &kCFTypeDictionaryKeyCallBacks,
241						  &kCFTypeDictionaryValueCallBacks);
242			CFArrayAppendValue(nodes, node);
243			CFRelease(node);
244		}
245
246		if (!isA_CFDictionary(node)) {
247			_SCErrorSet(kSCStatusNoKey);
248			goto done;
249		}
250
251		if ((i < nElements - 1) &&
252		    CFDictionaryGetValueIfPresent(node, kSCResvLink, (const void **)&link)) {
253			/*
254			 * if not the last path component and this
255			 * element is a link
256			 */
257			CFMutableArrayRef	linkElements;
258
259			if (++nLinks > MAXLINKS) {
260				/* if we are chasing our tail */
261				_SCErrorSet(kSCStatusMaxLink);
262				goto done;
263			}
264
265			linkElements = normalizePath(link);
266			if (linkElements == NULL) {
267				/* if the link is bad */
268				_SCErrorSet(kSCStatusNoKey);
269				goto done;
270			}
271
272			CFArrayAppendArray(linkElements,
273					   elements,
274					   CFRangeMake(i + 1, nElements-i - 1));
275			CFRelease(elements);
276			elements = linkElements;
277
278			CFRelease(nodes);
279			nodes = NULL;
280			goto restart;
281		}
282	}
283
284	/*
285	 * make sure that the last component doesn't step on top
286	 * of a non-dictionary component.
287	 */
288	element = CFArrayGetValueAtIndex(elements, nElements - 1);
289	if (nElements > 1) {
290		node = CFArrayGetValueAtIndex(nodes, nElements - 2);
291		node = CFDictionaryGetValue(node, element);
292	} else {
293		node = SCPreferencesGetValue(prefs, element);
294	}
295	if ((node != NULL) && !isA_CFDictionary(node)) {
296		// we won't step on a non-dictionary component
297		_SCErrorSet(kSCStatusInvalidArgument);
298		goto done;
299	}
300
301	if (entity != NULL) {
302		newEntity = CFRetain(entity);
303	}
304	for (i = nElements - 1; i >= 0; i--) {
305		element = CFArrayGetValueAtIndex(elements, i);
306		if (i == 0) {
307			if (newEntity != NULL) {
308				ok = SCPreferencesSetValue(prefs, element, newEntity);
309			} else {
310				ok = SCPreferencesRemoveValue(prefs, element);
311			}
312		} else {
313			CFMutableDictionaryRef	newNode;
314
315			node    = CFArrayGetValueAtIndex(nodes, i - 1);
316			newNode = CFDictionaryCreateMutableCopy(NULL, 0, node);
317			if (newEntity != NULL) {
318				CFDictionarySetValue(newNode, element, newEntity);
319				CFRelease(newEntity);
320			} else {
321				CFDictionaryRemoveValue(newNode, element);
322				if (CFDictionaryGetCount(newNode) == 0) {
323					// prune the (now empty) parent
324					CFRelease(newNode);
325					newNode = NULL;
326				}
327			}
328			newEntity = newNode;
329		}
330	}
331	if (newEntity != NULL) {
332		CFRelease(newEntity);
333	}
334
335    done :
336
337	if (nodes != NULL)	CFRelease(nodes);
338	CFRelease(elements);
339	return ok;
340}
341
342
343CFStringRef
344SCPreferencesPathCreateUniqueChild(SCPreferencesRef	prefs,
345				   CFStringRef		prefix)
346{
347	CFStringRef             child;
348	CFStringRef		newPath		= NULL;
349	CFDictionaryRef		newDict;
350	CFUUIDRef               uuid;
351	CFDictionaryRef		entity;
352
353	if (prefs == NULL) {
354		/* sorry, you must provide a session */
355		_SCErrorSet(kSCStatusNoPrefsSession);
356		return NULL;
357	}
358
359	if (getPath(prefs, prefix, &entity)) {
360		// if prefix path exists
361		if (CFDictionaryContainsKey(entity, kSCResvLink)) {
362			/* the path is a link... */
363			_SCErrorSet(kSCStatusFailed);
364			return NULL;
365		}
366	} else if (SCError() != kSCStatusNoKey) {
367		// if any error except for a missing prefix path component
368		return NULL;
369	}
370
371	uuid    = CFUUIDCreate(NULL);
372	child   = CFUUIDCreateString(NULL, uuid);
373	newPath = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), prefix, child);
374	CFRelease(child);
375	CFRelease(uuid);
376
377	/* save a new/empty dictionary */
378	newDict = CFDictionaryCreate(NULL,
379				     NULL, NULL, 0,
380				     &kCFTypeDictionaryKeyCallBacks,
381				     &kCFTypeDictionaryValueCallBacks);
382	assert(newDict != NULL);
383
384	if (!setPath(prefs, newPath, newDict)) {
385		CFRelease(newPath);
386		newPath = NULL;
387	}
388	CFRelease(newDict);
389
390	return newPath;
391}
392
393
394CFDictionaryRef
395SCPreferencesPathGetValue(SCPreferencesRef	prefs,
396			  CFStringRef		path)
397{
398	CFDictionaryRef	entity;
399	CFStringRef	entityLink;
400
401	if (prefs == NULL) {
402		/* sorry, you must provide a session */
403		_SCErrorSet(kSCStatusNoPrefsSession);
404		return NULL;
405	}
406
407	if (!getPath(prefs, path, &entity)) {
408		return NULL;
409	}
410
411	if (isA_CFDictionary(entity) &&
412	    (CFDictionaryGetValueIfPresent(entity, kSCResvLink, (const void **)&entityLink))) {
413		/* if this is a dictionary AND it is a link */
414		if (!getPath(prefs, entityLink, &entity)) {
415			/* if it was a bad link */
416			return NULL;
417		}
418	}
419
420	return entity;
421}
422
423
424CFStringRef
425SCPreferencesPathGetLink(SCPreferencesRef	prefs,
426			 CFStringRef		path)
427{
428	CFDictionaryRef	entity;
429	CFStringRef	entityLink;
430
431	if (prefs == NULL) {
432		/* sorry, you must provide a session */
433		_SCErrorSet(kSCStatusNoPrefsSession);
434		return NULL;
435	}
436
437	if (!getPath(prefs, path, &entity)) {
438		return NULL;
439	}
440
441	if (isA_CFDictionary(entity) &&
442	    (CFDictionaryGetValueIfPresent(entity, kSCResvLink, (const void **)&entityLink))) {
443		/* if this is a dictionary AND it is a link */
444		return entityLink;
445	}
446
447	return NULL;
448}
449
450
451Boolean
452SCPreferencesPathSetValue(SCPreferencesRef	prefs,
453			  CFStringRef		path,
454			  CFDictionaryRef	value)
455{
456	Boolean			ok;
457
458	if (prefs == NULL) {
459		/* sorry, you must provide a session */
460		_SCErrorSet(kSCStatusNoPrefsSession);
461		return FALSE;
462	}
463
464#define	NETPREF_NEEDS_REPAIR
465#ifdef	NETPREF_NEEDS_REPAIR
466	if (CFEqual(path, CFSTR("/CurrentSet")) && isA_CFString(value)) {
467		static Boolean	warned	= FALSE;
468		if (!warned) {
469			SCPrint(TRUE, stderr, CFSTR("SCPreferencesPathSetValue(, %@, ) called with non-dictionary value\n"), path);
470			warned = TRUE;
471		}
472		return SCPreferencesSetValue(prefs, CFSTR("CurrentSet"), value);
473	}
474#endif	// NETPREF_NEEDS_REPAIR
475
476	if (!isA_CFDictionary(value)) {
477#ifdef	NETPREF_NEEDS_REPAIR
478SCPrint(TRUE, stderr, CFSTR("SCPreferencesPathSetValue(, %@, ) called with non-dictionary value\n"), path);
479#endif	// NETPREF_NEEDS_REPAIR
480		_SCErrorSet(kSCStatusInvalidArgument);
481		return FALSE;
482	}
483
484	ok = setPath(prefs, path, value);
485	return ok;
486}
487
488
489Boolean
490SCPreferencesPathSetLink(SCPreferencesRef	prefs,
491			 CFStringRef		path,
492			 CFStringRef		link)
493{
494	CFMutableDictionaryRef	dict;
495	CFDictionaryRef		entity;
496	Boolean			ok;
497
498	if (prefs == NULL) {
499		/* sorry, you must provide a session */
500		_SCErrorSet(kSCStatusNoPrefsSession);
501		return FALSE;
502	}
503
504	if (!isA_CFString(link)) {
505		_SCErrorSet(kSCStatusInvalidArgument);
506		return FALSE;
507	}
508
509	if (!getPath(prefs, link, &entity)) {
510		// if bad link
511		return FALSE;
512	}
513
514	dict = CFDictionaryCreateMutable(NULL,
515					 0,
516					 &kCFTypeDictionaryKeyCallBacks,
517					 &kCFTypeDictionaryValueCallBacks);
518	CFDictionarySetValue(dict, kSCResvLink, link);
519	ok = setPath(prefs, path, dict);
520	CFRelease(dict);
521
522	return ok;
523}
524
525
526Boolean
527SCPreferencesPathRemoveValue(SCPreferencesRef	prefs,
528			     CFStringRef	path)
529{
530	CFMutableArrayRef	elements	= NULL;
531	Boolean			ok		= FALSE;
532	CFDictionaryRef		value;
533
534	if (prefs == NULL) {
535		/* sorry, you must provide a session */
536		_SCErrorSet(kSCStatusNoPrefsSession);
537		return FALSE;
538	}
539
540	if (!getPath(prefs, path, &value)) {
541		// if no such path
542		return FALSE;
543	}
544
545	elements = normalizePath(path);
546	if (elements == NULL) {
547		_SCErrorSet(kSCStatusNoKey);
548		return FALSE;
549	}
550
551	ok = setPath(prefs, path, NULL);
552
553	CFRelease(elements);
554	return ok;
555}
556