xref: /NextBSD/usr.sbin/notifyd/service.c (revision 33da5adc555b3bc29986eeadca03829e4ad06b1e)
1 /*
2  * Copyright (c) 2003-2010 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 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <assert.h>
29 #include <asl.h>
30 #include "notify.h"
31 #include "notifyd.h"
32 #include "service.h"
33 #include "pathwatch.h"
34 #include "timer.h"
35 
36 #define NOTIFY_PATH_SERVICE "path:"
37 #define NOTIFY_PATH_SERVICE_LEN 5
38 #define NOTIFY_TIMER_SERVICE "timer:"
39 #define NOTIFY_TIMER_SERVICE_LEN 6
40 
41 /* Libinfo global */
42 extern uint32_t gL1CacheEnabled;
43 
44 static uint32_t
service_type(const char * name)45 service_type(const char *name)
46 {
47 	uint32_t len;
48 
49 	len = SERVICE_PREFIX_LEN;
50 	if (strncmp(name, SERVICE_PREFIX, len)) return SERVICE_TYPE_NONE;
51 	else if (!strncmp(name + len, NOTIFY_PATH_SERVICE, NOTIFY_PATH_SERVICE_LEN)) return SERVICE_TYPE_PATH_PRIVATE;
52 	else if (!strncmp(name + len, NOTIFY_TIMER_SERVICE, NOTIFY_TIMER_SERVICE_LEN)) return SERVICE_TYPE_TIMER_PRIVATE;
53 
54 	return SERVICE_TYPE_NONE;
55 }
56 
57 /*
58  * Request notifications for changes on a filesystem path.
59  * This creates a new pathwatch node and sets it to post notifications for
60  * the specified name.
61  *
62  * If the notify name already has a pathwatch node for this path, this routine
63  * does nothing and allows the client to piggypack on the existing path watcher.
64  *
65  * Note that this routine is only called for path monitoring as directed by
66  * a "monitor" command in /etc/notify.conf, so only an admin can set up a path
67  * that gets public notifications.  A client being serviced by the server-side
68  * routines in notify_proc.c will only be able to register for a private
69  * (per-client) notification for a path.  This prevents a client from
70  * piggybacking on another client's notifications, and thus prevents the client
71  * from getting notifications for a path to which they don't have access.
72  */
73 int
service_open_path(const char * name,const char * path,uid_t uid,gid_t gid)74 service_open_path(const char *name, const char *path, uid_t uid, gid_t gid)
75 {
76 	name_info_t *n;
77 	svc_info_t *info;
78 	path_node_t *node;
79 
80 	call_statistics.service_path++;
81 
82 	if (path == NULL) return NOTIFY_STATUS_INVALID_REQUEST;
83 
84 	n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name);
85 	if (n == NULL) return NOTIFY_STATUS_INVALID_NAME;
86 
87 	if (n->private != NULL)
88 	{
89 		/* a notify key may only have one service associated with it */
90 		info = (svc_info_t *)n->private;
91 		if (info->type != SERVICE_TYPE_PATH_PUBLIC) return NOTIFY_STATUS_INVALID_REQUEST;
92 
93 		/* the client must be asking for the same path that is being monitored */
94 		node = (path_node_t *)info->private;
95 		if (strcmp(path, node->path)) return NOTIFY_STATUS_INVALID_REQUEST;
96 
97 		/* the name is already getting notifications for this path */
98 		return NOTIFY_STATUS_OK;
99 	}
100 
101 #ifdef notyet
102 	node = path_node_create(path, uid, gid, PATH_NODE_ALL, dispatch_get_main_queue());
103 #else
104 	node = NULL;
105 #endif
106 	if (node == NULL) return NOTIFY_STATUS_FAILED;
107 
108 	node->contextp = strdup(name);
109 
110 	info = (svc_info_t *)calloc(1, sizeof(svc_info_t));
111 	assert(info != NULL);
112 
113 	info->type = SERVICE_TYPE_PATH_PUBLIC;
114 	info->private = node;
115 	n->private = info;
116 
117 	dispatch_source_set_event_handler(node->src, ^{
118 		dispatch_async(global.work_q, ^{
119 			if (0 == dispatch_source_testcancel(node->src))
120 			{
121 				daemon_post((const char *)node->contextp, uid, gid);
122 			}
123 		});
124 	});
125 
126 	dispatch_resume(node->src);
127 
128 	return NOTIFY_STATUS_OK;
129 }
130 
131 /*
132  * The private (per-client) path watch service.
133  */
134 int
service_open_path_private(const char * name,client_t * c,const char * path,uid_t uid,gid_t gid,uint32_t flags)135 service_open_path_private(const char *name, client_t *c, const char *path, uid_t uid, gid_t gid, uint32_t flags)
136 {
137 	name_info_t *n;
138 	svc_info_t *info;
139 	path_node_t *node = NULL;
140 
141 	(void)uid;
142 	(void)gid;
143 
144 	call_statistics.service_path++;
145 
146 	if (path == NULL) return NOTIFY_STATUS_INVALID_REQUEST;
147 
148 	n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name);
149 	if (n == NULL) return NOTIFY_STATUS_INVALID_NAME;
150 	if (c == NULL) return NOTIFY_STATUS_FAILED;
151 
152 	if (c->private != NULL)
153 	{
154 		/* a client may only have one service */
155 		info = (svc_info_t *)c->private;
156 		if (info->type != SERVICE_TYPE_PATH_PRIVATE) return NOTIFY_STATUS_INVALID_REQUEST;
157 
158 		/* the client must be asking for the same path that is being monitored */
159 		node = (path_node_t *)info->private;
160 		if (strcmp(path, node->path)) return NOTIFY_STATUS_INVALID_REQUEST;
161 
162 		/* the client is already getting notifications for this path */
163 		return NOTIFY_STATUS_OK;
164 	}
165 
166 	if (flags == 0) flags = PATH_NODE_ALL;
167 
168 //	node = path_node_create(path, uid, gid, flags, dispatch_get_main_queue());
169 	if (node == NULL) return NOTIFY_STATUS_FAILED;
170 
171 	node->context64 = c->client_id;
172 
173 	info = (svc_info_t *)calloc(1, sizeof(svc_info_t));
174 	assert(info != NULL);
175 
176 	info->type = SERVICE_TYPE_PATH_PRIVATE;
177 	info->private = node;
178 	c->private = info;
179 
180 	dispatch_source_set_event_handler(node->src, ^{
181 		dispatch_async(global.work_q, ^{
182 			if (0 == dispatch_source_testcancel(node->src))
183 			{
184 				daemon_post_client(node->context64);
185 			}
186 		});
187 	});
188 
189 	dispatch_resume(node->src);
190 
191 	return NOTIFY_STATUS_OK;
192 }
193 
194 /* format: [+]nnnn[s|m|h|d] */
195 static int
parse_single_arg(const char * arg,int relative_ok,time_t * t)196 parse_single_arg(const char *arg, int relative_ok, time_t *t)
197 {
198 	const char *p, *q;
199 	time_t now, val;
200 
201 	if (arg == NULL) return -1;
202 	p = arg;
203 
204 	now = 0;
205 
206 	if ((relative_ok != 0) && ((*p == '+') || (*p == '-')))
207 	{
208 		p++;
209 		now = time(NULL);
210 	}
211 
212 	if ((*p < '0') || (*p > '9')) return -1;
213 
214 	q = strchr(p, '.');
215 	if (q != NULL) q--;
216 	else q = arg + strlen(arg) - 1;
217 
218 #ifdef __LP64__
219 	val = (time_t)atoll(p);
220 #else
221 	val = (time_t)atoi(p);
222 #endif
223 
224 	if ((*q >= '0') && (*q <= '9'))
225 	{}
226 	else if (*q == 's')
227 	{}
228 	else if (*q == 'm')
229 	{
230 		val *= 60;
231 	}
232 	else if (*q == 'h')
233 	{
234 		val *= 3600;
235 	}
236 	else if (*q == 'd')
237 	{
238 		val *= 86400;
239 	}
240 	else
241 	{
242 		return -1;
243 	}
244 
245 	if (*arg == '-') *t = now - val;
246 	else *t = now + val;
247 
248 	return 0;
249 }
250 
251 static uint32_t
parse_timer_args(const char * args,time_t * s,time_t * f,time_t * e,int32_t * d)252 parse_timer_args(const char *args, time_t *s, time_t *f, time_t *e, int32_t *d)
253 {
254 	char *p;
255 	uint32_t t;
256 
257 	if (args == NULL) return TIME_EVENT_NONE;
258 
259 	/* first arg is start time */
260 	if (parse_single_arg(args, 1, s) != 0) return TIME_EVENT_NONE;
261 	t = TIME_EVENT_ONESHOT;
262 
263 	p = strchr(args, '.');
264 	if (p != NULL)
265 	{
266 		/* second arg is frequency */
267 		p++;
268 		if (parse_single_arg(p, 0, f) != 0) return TIME_EVENT_NONE;
269 		t = TIME_EVENT_CLOCK;
270 
271 		p = strchr(p, '.');
272 		if (p != NULL)
273 		{
274 			/* third arg is end time */
275 			p++;
276 			if (parse_single_arg(args, 1, e) != 0) return TIME_EVENT_NONE;
277 
278 			p = strchr(p, '.');
279 			if (p != NULL)
280 			{
281 				/* fourth arg is day number */
282 				p++;
283 				*d = atoi(p);
284 				t = TIME_EVENT_CAL;
285 			}
286 		}
287 	}
288 
289 	if (f == 0) t = TIME_EVENT_ONESHOT;
290 
291 	return t;
292 }
293 
294 int
service_open_timer(const char * name,const char * args)295 service_open_timer(const char *name, const char *args)
296 {
297 	uint32_t t;
298 	time_t s, f, e;
299 	int32_t d;
300 	name_info_t *n;
301 	svc_info_t *info;
302 	timer_event_t *timer;
303 
304 	call_statistics.service_timer++;
305 
306 	n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name);
307 	if (n == NULL) return NOTIFY_STATUS_INVALID_NAME;
308 
309 	s = f = e = 0;
310 	d = 0;
311 
312 	t = parse_timer_args(args, &s, &f, &e, &d);
313 	if (t == TIME_EVENT_NONE) return NOTIFY_STATUS_INVALID_REQUEST;
314 
315 	if (n->private != NULL)
316 	{
317 		/* a notify key may only have one service associated with it */
318 		info = (svc_info_t *)n->private;
319 		if (info->type != SERVICE_TYPE_TIMER_PUBLIC) return NOTIFY_STATUS_INVALID_REQUEST;
320 
321 		/* the client must be asking for the same timer that is active */
322 		timer = (timer_event_t *)info->private;
323 		if ((timer->type != t) || (timer->start != s) || (timer->freq != f) || (timer->end != e) || (timer->day != d)) return NOTIFY_STATUS_INVALID_REQUEST;
324 
325 		/* the name is already getting notifications for this timer */
326 		return NOTIFY_STATUS_OK;
327 	}
328 
329 	switch (t)
330 	{
331 		case TIME_EVENT_ONESHOT:
332 		{
333 			timer = timer_oneshot(s, dispatch_get_main_queue());
334 			break;
335 		}
336 		case TIME_EVENT_CLOCK:
337 		{
338 			timer = timer_clock(s, f, e, dispatch_get_main_queue());
339 			break;
340 		}
341 		case TIME_EVENT_CAL:
342 		{
343 			timer = timer_calendar(s, f, d, e, dispatch_get_main_queue());
344 			break;
345 		}
346 		default:
347 		{
348 			return NOTIFY_STATUS_FAILED;
349 		}
350 	}
351 
352 	if (timer == NULL) return NOTIFY_STATUS_FAILED;
353 	timer->contextp = strdup(name);
354 
355 	info = (svc_info_t *)calloc(1, sizeof(svc_info_t));
356 	assert(info != NULL);
357 
358 	info->type = SERVICE_TYPE_TIMER_PUBLIC;
359 	info->private = timer;
360 	n->private = info;
361 
362 	dispatch_source_set_event_handler(timer->src, ^{
363 		dispatch_async(global.work_q, ^{
364 			if (0 == dispatch_source_testcancel(timer->src))
365 			{
366 				daemon_post((const char *)timer->contextp, 0, 0);
367 			}
368 		});
369 	});
370 
371 	dispatch_resume(timer->src);
372 
373 	return NOTIFY_STATUS_OK;
374 }
375 
376 int
service_open_timer_private(const char * name,client_t * c,const char * args)377 service_open_timer_private(const char *name, client_t *c, const char *args)
378 {
379 	uint32_t t;
380 	time_t s, f, e;
381 	int32_t d;
382 	name_info_t *n;
383 	svc_info_t *info;
384 	timer_event_t *timer;
385 
386 	call_statistics.service_timer++;
387 
388 	n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name);
389 	if (n == NULL) return NOTIFY_STATUS_INVALID_NAME;
390 	if (c == NULL) return NOTIFY_STATUS_FAILED;
391 
392 	s = f = e = 0;
393 	d = 0;
394 
395 	t = parse_timer_args(args, &s, &f, &e, &d);
396 	if (t == TIME_EVENT_NONE) return NOTIFY_STATUS_INVALID_REQUEST;
397 
398 	if (c->private != NULL)
399 	{
400 		/* a client may only have one service */
401 		info = (svc_info_t *)c->private;
402 		if (info->type != SERVICE_TYPE_TIMER_PRIVATE) return NOTIFY_STATUS_INVALID_REQUEST;
403 
404 		/* the client must be asking for the same timer that is active */
405 		timer = (timer_event_t *)info->private;
406 		if ((timer->type != t) || (timer->start != s) || (timer->freq != f) || (timer->end != e) || (timer->day != d)) return NOTIFY_STATUS_INVALID_REQUEST;
407 
408 		/* the client is already getting notifications for this timer */
409 		return NOTIFY_STATUS_OK;
410 	}
411 
412 	switch (t)
413 	{
414 		case TIME_EVENT_ONESHOT:
415 		{
416 			timer = timer_oneshot(s, dispatch_get_main_queue());
417 			break;
418 		}
419 		case TIME_EVENT_CLOCK:
420 		{
421 			timer = timer_clock(s, f, e, dispatch_get_main_queue());
422 			break;
423 		}
424 		case TIME_EVENT_CAL:
425 		{
426 			timer = timer_calendar(s, f, d, e, dispatch_get_main_queue());
427 			break;
428 		}
429 		default:
430 		{
431 			return NOTIFY_STATUS_FAILED;
432 		}
433 	}
434 
435 	if (timer == NULL) return NOTIFY_STATUS_FAILED;
436 	timer->context64 = c->client_id;
437 
438 	info = (svc_info_t *)calloc(1, sizeof(svc_info_t));
439 	assert(info != NULL);
440 
441 	info->type = SERVICE_TYPE_TIMER_PRIVATE;
442 	info->private = timer;
443 	c->private = info;
444 
445 	dispatch_source_set_event_handler(timer->src, ^{
446 		dispatch_async(global.work_q, ^{
447 			if (0 == dispatch_source_testcancel(timer->src))
448 			{
449 				daemon_post_client(timer->context64);
450 			}
451 		});
452 	});
453 
454 	dispatch_resume(timer->src);
455 
456 	return NOTIFY_STATUS_OK;
457 }
458 
459 /* called from server-side routines in notify_proc - services are private to the client */
460 int
service_open(const char * name,client_t * client,uint32_t uid,uint32_t gid)461 service_open(const char *name, client_t *client, uint32_t uid, uint32_t gid)
462 {
463 	uint32_t t, flags;
464     char *p, *q;
465 
466 	t = service_type(name);
467 
468 	switch (t)
469 	{
470 		case SERVICE_TYPE_NONE:
471 		{
472 			return NOTIFY_STATUS_OK;
473 		}
474 		case SERVICE_TYPE_PATH_PRIVATE:
475 		{
476 			p = strchr(name, ':');
477 			if (p != NULL) p++;
478 
479 			flags = 0;
480 
481 			q = strchr(p, ':');
482 			if (q != NULL)
483 			{
484 				flags = strtol(p, NULL, 0);
485 				p = q + 1;
486 			}
487 
488 			return service_open_path_private(name, client, p, uid, gid, flags);
489 		}
490 		case SERVICE_TYPE_TIMER_PRIVATE:
491 		{
492 			p = strchr(name, ':');
493 			if (p != NULL) p++;
494 			return service_open_timer_private(name, client, p);
495 		}
496 		default:
497 		{
498 			return NOTIFY_STATUS_INVALID_REQUEST;
499 		}
500 	}
501 
502 	return NOTIFY_STATUS_INVALID_REQUEST;
503 }
504 
505 void
service_close(svc_info_t * info)506 service_close(svc_info_t *info)
507 {
508 	if (info == NULL) return;
509 
510 	switch (info->type)
511 	{
512 		case SERVICE_TYPE_PATH_PUBLIC:
513 		case SERVICE_TYPE_PATH_PRIVATE:
514 		{
515 //			path_node_close((path_node_t *)info->private);
516 			break;
517 		}
518 		case SERVICE_TYPE_TIMER_PUBLIC:
519 		case SERVICE_TYPE_TIMER_PRIVATE:
520 		{
521 			timer_close((timer_event_t *)info->private);
522 			break;
523 		}
524 		default:
525 		{
526 		}
527 	}
528 
529 	free(info);
530 }
531