1/*
2 * Copyright (c) 2012, 2013 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#include <CommonCrypto/CommonDigest.h>
24#include <dirent.h>
25#include <notify.h>
26#include <sys/param.h>
27#include <sys/queue.h>
28#include <sys/types.h>
29#include <sys/stat.h>
30#include <SystemConfiguration/SCPrivate.h>
31#include <SystemConfiguration/scprefs_observer.h>
32
33#pragma mark -
34#pragma mark Utils
35
36static void
37iterate_dir(const char *d_name, const char *f_name,
38	    CC_SHA1_CTX *ctxP, Boolean *found)
39{
40	DIR *dir;
41	struct dirent * dp;
42
43	dir = opendir(d_name);
44
45	if (dir == NULL) {
46		/* if directory path does not exist */
47		return;
48	}
49
50	while ((dp = readdir(dir)) != NULL) {
51		char		full_path[MAXPATHLEN];
52		struct stat	s;
53
54		if ((strcmp(dp->d_name, ".") == 0) ||
55		    (strcmp(dp->d_name, "..") == 0)) {
56			continue;
57		}
58
59		/* check path */
60		snprintf(full_path, sizeof(full_path), "%s/%s", d_name, dp->d_name);
61		if (stat(full_path, &s) == 0) {
62			if (S_ISDIR(s.st_mode)) {
63				// if sub-directory, iterate
64				iterate_dir(full_path, f_name, ctxP, found);
65			} else if (strcmp(f_name, dp->d_name) == 0) {
66				/*
67				 * if this is the requested file, include
68				 * the path and last modification time in
69				 * the digest
70				*/
71				CC_SHA1_Update(ctxP, full_path, (CC_LONG)strlen(full_path));
72				CC_SHA1_Update(ctxP,
73					       (void *)&s.st_mtimespec.tv_sec,
74					       sizeof(s.st_mtimespec.tv_sec));
75				*found = TRUE;
76			}
77		}
78	}
79	closedir(dir);
80	return;
81}
82
83static CF_RETURNS_RETAINED CFDataRef
84build_digest(const char *top_dir, const char *file)
85{
86	unsigned char	bytes[CC_SHA1_DIGEST_LENGTH];
87	CC_SHA1_CTX	ctx;
88	CFDataRef	digest = NULL;
89	Boolean		found = FALSE;
90
91	CC_SHA1_Init(&ctx);
92	iterate_dir(top_dir, file, &ctx, &found);
93	CC_SHA1_Final(bytes, &ctx);
94	if (found == TRUE) {
95		digest = CFDataCreate(NULL, bytes, sizeof(bytes));
96	}
97	return (digest);
98}
99
100#pragma mark -
101#pragma mark perfs_observer Private
102
103struct _scprefs_observer_t {
104	_scprefs_observer_type			type;
105	dispatch_block_t			block;
106	CFDataRef				digest;
107	SLIST_ENTRY(_scprefs_observer_t)	next;
108	dispatch_queue_t			queue;
109	char					file[0];
110};
111
112#define MOBILE_PREFERENCES_PATH "/var/mobile/Library/Preferences"
113static const char *
114prefs_observer_get_prefs_path(scprefs_observer_t observer)
115{
116	switch (observer->type) {
117#if	!TARGET_OS_IPHONE
118	case scprefs_observer_type_mcx:
119		return ("/Library/Managed Preferences");
120#else	// !TARGET_OS_IPHONE
121	case scprefs_observer_type_global:
122		return ("/Library/Managed Preferences");
123	case scprefs_observer_type_profile:
124		return MOBILE_PREFERENCES_PATH;
125#endif	// !TARGET_OS_IPHONE
126	default:
127		return (NULL);
128	}
129}
130
131/*
132 * Iterate through all of the directories and subdirectories.
133 * If the file within those directories has changed,
134 * then generate the notification.
135 */
136static Boolean
137has_changed(scprefs_observer_t  observer) {
138	Boolean         changed;
139	CFDataRef       digest = NULL;
140	const char *    starting_path;
141
142	starting_path = prefs_observer_get_prefs_path(observer);
143
144	digest = build_digest(starting_path, observer->file);
145
146	/* compare old vs. new digest */
147	changed = _SC_CFEqual(digest, observer->digest)?FALSE:TRUE;
148
149	/* save the digest */
150	if (observer->digest != NULL) {
151		CFRelease(observer->digest);
152	}
153
154	observer->digest = digest;
155
156	SCLog(_sc_verbose, LOG_NOTICE, CFSTR("The following file: %s, %s \n"),
157	      observer->file, (changed)?"has changed":"has not changed");
158	return (changed);
159}
160
161static dispatch_queue_t
162prefs_observer_queue;
163
164/* This holds the list of the observers */
165static SLIST_HEAD(mylist, _scprefs_observer_t) head;
166
167static void
168prefs_observer_release(scprefs_observer_t observer)
169{
170	SLIST_REMOVE(&head, observer, _scprefs_observer_t, next);
171
172	/* Now free the observer */
173	if (observer->digest != NULL) {
174		CFRelease(observer->digest);
175	}
176
177	free(observer);
178}
179
180static void
181prefs_observer_handle_notifications()
182{
183	scprefs_observer_t observer;
184
185	SCLog(_sc_verbose, LOG_NOTICE, CFSTR("PrefsObserver Notification received \n"));
186
187	SLIST_FOREACH(observer, &head, next) {
188		/* if the preferences plist has changed,
189		 * called the block */
190		if (has_changed(observer)) {
191			dispatch_async(observer->queue, observer->block);
192		}
193	}
194}
195
196#define PREFS_OBSERVER_KEY "com.apple.ManagedConfiguration.profileListChanged"
197static void
198_prefs_observer_init()
199{
200	static int token;
201
202	prefs_observer_queue = dispatch_queue_create("com.apple.SystemConfiguration.SCPreferencesObserver", NULL);
203
204	SLIST_INIT(&head);
205
206	notify_register_dispatch(PREFS_OBSERVER_KEY,
207			     &token,
208			     prefs_observer_queue,
209			     ^(int token) { prefs_observer_handle_notifications(); });
210}
211
212static scprefs_observer_t
213prefs_observer_priv_create(_scprefs_observer_type type,
214			   const char *plist_name,
215			   dispatch_queue_t queue,
216			   dispatch_block_t block)
217{
218	scprefs_observer_t	observer;
219	size_t			path_buflen;
220
221	path_buflen = strlen(plist_name) + 1;
222
223	observer = (scprefs_observer_t)malloc(sizeof(struct _scprefs_observer_t) + path_buflen);
224	bzero((void *)observer, sizeof(struct _scprefs_observer_t));
225
226	/* Create the observer */
227	observer->type = type;
228	strlcpy(observer->file, plist_name, path_buflen);
229
230	observer->queue = queue;
231	observer->block = Block_copy(block);
232
233	return (observer);
234}
235
236#pragma mark -
237#pragma mark perfs_observer Public SPI
238scprefs_observer_t
239_scprefs_observer_watch(_scprefs_observer_type type, const char *plist_name,
240			   dispatch_queue_t queue, dispatch_block_t block)
241{
242	scprefs_observer_t elem;
243	static dispatch_once_t initialized;
244
245	dispatch_once(&initialized, ^{
246		_prefs_observer_init();
247	});
248
249	elem = prefs_observer_priv_create(type, plist_name, queue, block);
250	SCLog(_sc_verbose, LOG_NOTICE, CFSTR("Created a new element to watch for %s \n"),
251	      elem->file);
252
253	dispatch_sync(prefs_observer_queue, ^{
254		/* Enqueue the request */
255		SLIST_INSERT_HEAD(&head, elem, next);
256	});
257	return (elem);
258}
259
260/* This will cancel/deregister the given watcher.  This will be synchronized on the
261 * internally created queue. */
262void
263_scprefs_observer_cancel(scprefs_observer_t observer)
264{
265	dispatch_sync(prefs_observer_queue, ^{
266		prefs_observer_release((scprefs_observer_t)observer);
267	});
268}
269
270#pragma mark -
271
272#ifdef TEST_MAIN
273int main()
274{
275	int random = 1;
276
277	_sc_verbose = 1;
278
279	dispatch_queue_t q = dispatch_queue_create("com.apple.SystemConfiguration.PrefsObserver.mainQ", NULL);
280
281	dispatch_queue_t q1 = dispatch_queue_create("com.apple.SystemConfiguration.PrefsObserver.testQ1", NULL);
282
283	dispatch_block_t b1 = ^{
284	printf("Block 1 executed \n");
285	};
286
287	dispatch_queue_t q2 = dispatch_queue_create("com.apple.SystemConfiguration.PrefsObserver.testQ2", NULL);
288	dispatch_block_t b2  = ^{
289	printf("Block 2 executed \n");
290	};
291
292	dispatch_queue_t q3 =  dispatch_queue_create("com.apple.SystemConfiguration.PrefsObserver.testQ2", NULL);
293
294	dispatch_block_t b3 = ^{
295	printf("Block 3 executed \n");
296	};
297
298	__block scprefs_observer_t observer1 = _scprefs_observer_watch(scprefs_observer_type_mcx, "com.apple.SystemConfiguration", q1, b1);
299
300	__block scprefs_observer_t observer2 = _scprefs_observer_watch(scprefs_observer_type_mcx, "foo", q2, b2);
301
302	__block scprefs_observer_t observer3 = _scprefs_observer_watch(scprefs_observer_type_mcx, "bar", q3, b3);
303
304	__block scprefs_observer_t observer = NULL;
305
306	while (1) {
307	switch (random % 3)
308	{
309	    case 0:
310		dispatch_async(q, ^{
311			_SC_prefs_observer_cancel(observer1);
312			observer1 = NULL;
313		});
314		dispatch_async(q, ^{
315			if (observer != NULL)  _SC_prefs_observer_cancel(observer);
316			observer = _SC_prefs_observer_watch(SC_prefs_observer_type_mcx, "test", q2, b2);
317		});
318		dispatch_sync(q, ^{
319			observer1 = observer;
320		});
321		sleep(random);
322		break;
323	    case 1:
324		dispatch_async(q, ^{
325			_SC_prefs_observer_cancel(observer2);
326		});
327		dispatch_async(q, ^{
328			if (observer != NULL) _SC_prefs_observer_cancel(observer);
329		});
330		dispatch_sync(q, ^{
331			observer = _SC_prefs_observer_watch(SC_prefs_observer_type_mcx, "test", q2, b2);
332		});
333		sleep(random);
334		break;
335	    case 2:
336		sleep (random);
337	    default:
338		break;
339	}
340	random++;
341	}
342	dispatch_main();
343}
344#endif
345