1/*
2 * Copyright (c) 2004-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
24#include <TargetConditionals.h>
25
26#include <sys/types.h>
27#include <sys/stat.h>
28#include <sys/socket.h>
29#include <sys/un.h>
30#include <sys/uio.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <unistd.h>
35#include <fcntl.h>
36#include <errno.h>
37#include <netdb.h>
38#include <notify.h>
39#include <pthread.h>
40#include <sys/acl.h>
41#include <dirent.h>
42#include <time.h>
43#include <membership.h>
44#include <configuration_profile.h>
45#include "daemon.h"
46#include <xpc/private.h>
47
48#define _PATH_WALL "/usr/bin/wall"
49#define NOTIFY_PATH_SERVICE "com.apple.system.notify.service.path:0x87:"
50
51#define MY_ID "asl_action"
52
53/* XXX add to asl.h */
54#define ASL_KEY_MODULE "ASLModule"
55
56#define MAX_FAILURES 5
57
58#define ACTION_STATUS_ERROR  -1
59#define ACTION_STATUS_OK      0
60
61#define IDLE_CLOSE 300
62
63#define forever for(;;)
64
65static dispatch_queue_t asl_action_queue;
66static dispatch_source_t checkpoint_timer;
67static time_t sweep_time = 0;
68
69#if TARGET_OS_EMBEDDED
70static dispatch_queue_t crashlog_queue;
71static dispatch_source_t crashlog_sentinel_src;
72static int crashlog_sentinel_fd = -1;
73static time_t crashmover_state = 0;
74static int crashmover_token = -1;
75#endif
76
77typedef struct store_data
78{
79	asl_file_t *store;
80	FILE *storedata;
81	uint64_t next_id;
82	time_t last_time;
83	uint32_t p_year;
84	uint32_t p_month;
85	uint32_t p_day;
86	dispatch_source_t monitor;
87} asl_action_store_data_t;
88
89typedef struct file_data
90{
91	int fd;
92	uint32_t last_hash;
93	uint32_t last_count;
94	time_t last_time;
95	char *last_msg;
96	dispatch_source_t dup_timer;
97	dispatch_source_t monitor;
98} asl_action_file_data_t;
99
100typedef struct set_param_data
101{
102	int token;
103} asl_action_set_param_data_t;
104
105static int action_asl_store_count;
106static bool store_has_logged;
107
108extern void db_save_message(aslmsg m);
109
110/* forward */
111static int _act_file_checkpoint_all(uint32_t force);
112static void _asl_action_post_process_rule(asl_out_module_t *m, asl_out_rule_t *r);
113static void _asl_action_close_idle_files(time_t idle_time);
114
115static void
116_act_out_set_param(asl_out_module_t *m, char *x, bool eval)
117{
118	char *s = x;
119	char **l;
120	uint32_t count, intval;
121
122	l = explode(s, " \t");
123	if (l == NULL) return;
124
125	for (count = 0; l[count] != NULL; count++);
126	if (count == 0)
127	{
128		free_string_list(l);
129		return;
130	}
131
132	if (!strcasecmp(l[0], "enable"))
133	{
134		/* = enable [1|0] */
135		if (count < 2) intval = 1;
136		else intval = atoi(l[1]);
137
138		if (!eval) intval = (intval == 0) ? 1 : 0;
139
140		if (intval == 0) m->flags &= ~MODULE_FLAG_ENABLED;
141		else m->flags|= MODULE_FLAG_ENABLED;
142		return;
143	}
144	else if (!strcasecmp(l[0], "disable"))
145	{
146		/* = disable [1|0] */
147		if (count < 2) intval = 1;
148		else intval = atoi(l[1]);
149
150		if (!eval) intval = (intval == 0) ? 1 : 0;
151
152		if (intval != 0) m->flags &= ~MODULE_FLAG_ENABLED;
153		else m->flags|= MODULE_FLAG_ENABLED;
154		return;
155	}
156
157	free_string_list(l);
158
159	if (!strcmp(m->name, ASL_MODULE_NAME))
160	{
161		/* Other parameters may be set by com.apple.asl module */
162		control_set_param(x, eval);
163	}
164}
165
166static void
167_act_notify(asl_out_module_t *m, asl_out_rule_t *r)
168{
169	if (m == NULL) return;
170	if ((m->flags & MODULE_FLAG_ENABLED) == 0) return;
171
172	if (r == NULL) return;
173	if (r->options == NULL) return;
174
175	notify_post(r->options);
176}
177
178static void
179_act_broadcast(asl_out_module_t *m, asl_out_rule_t *r, aslmsg msg)
180{
181#if !TARGET_OS_EMBEDDED
182	FILE *pw;
183	const char *val;
184
185	if (m == NULL) return;
186	if ((m->flags & MODULE_FLAG_ENABLED) == 0) return;
187
188	if (m->name == NULL) return;
189	if (r == NULL) return;
190	if (msg == NULL) return;
191
192	/* only base module (asl.conf) may broadcast */
193	if (strcmp(m->name, ASL_MODULE_NAME)) return;
194
195	val = r->options;
196	if (val == NULL) val = asl_get(msg, ASL_KEY_MSG);
197	if (val == NULL) return;
198
199	pw = popen(_PATH_WALL, "w");
200	if (pw == NULL)
201	{
202		asldebug("%s: error sending wall message: %s\n", MY_ID, strerror(errno));
203		return;
204	}
205
206	fprintf(pw, "%s", val);
207	pclose(pw);
208#endif
209}
210
211static void
212_act_access_control(asl_out_module_t *m, asl_out_rule_t *r, aslmsg msg)
213{
214	int32_t ruid, rgid;
215	char *p;
216
217	if (m == NULL) return;
218	if (m->name == NULL) return;
219	if (r == NULL) return;
220	if (msg == NULL) return;
221
222	/* only base module (asl.conf) may set access controls */
223	if (strcmp(m->name, ASL_MODULE_NAME)) return;
224
225	ruid = atoi(r->options);
226	rgid = -1;
227	p = strchr(r->options, ' ');
228	if (p == NULL) p = strchr(r->options, '\t');
229	if (p != NULL)
230	{
231		*p = '\0';
232		p++;
233		rgid = atoi(p);
234	}
235
236	if (ruid != -1) asl_set(msg, ASL_KEY_READ_UID, r->options);
237	if (p != NULL)
238	{
239		if (rgid != -1) asl_set(msg, ASL_KEY_READ_GID, p);
240		p--;
241		*p = ' ';
242	}
243}
244
245#if TARGET_OS_EMBEDDED
246static void
247_crashlog_sentinel_init(void)
248{
249	char path[MAXPATHLEN];
250
251	if (crashlog_sentinel_src != NULL) return;
252
253	snprintf(path, sizeof(path), "%s/com.apple.asl.%ld", _PATH_CRASHREPORTER, time(NULL));
254
255	crashlog_sentinel_fd = open(path, O_WRONLY | O_CREAT);
256	if (crashlog_sentinel_fd < 0)
257	{
258		char *str = NULL;
259		asprintf(&str, "[Sender syslogd] [Level 3] [PID %u] [Facility syslog] [Message Sentinel %s create/open failed (%s)]", global.pid, path, strerror(errno));
260		internal_log_message(str);
261		free(str);
262		return;
263	}
264
265	close(crashlog_sentinel_fd);
266
267	crashlog_sentinel_fd = open(path, O_EVTONLY, 0);
268	if (crashlog_sentinel_fd < 0)
269	{
270		char *str = NULL;
271		asprintf(&str, "[Sender syslogd] [Level 3] [PID %u] [Facility syslog] [Message Sentinel %s event/open failed (%s)]", global.pid, path, strerror(errno));
272		internal_log_message(str);
273		free(str);
274		return;
275	}
276
277	crashlog_sentinel_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, (uintptr_t)crashlog_sentinel_fd, DISPATCH_VNODE_DELETE, asl_action_queue);
278	if (crashlog_sentinel_src == NULL)
279	{
280		char *str = NULL;
281		asprintf(&str, "[Sender syslogd] [Level 3] [PID %u] [Facility syslog] [Message Sentinel %s dispatch_source_create failed]", global.pid, path);
282		internal_log_message(str);
283		free(str);
284		close(crashlog_sentinel_fd);
285		crashlog_sentinel_fd = -1;
286		return;
287	}
288
289	dispatch_source_set_event_handler(crashlog_sentinel_src, ^{
290		if (crashmover_state != 0)
291		{
292			asldebug("CrashMover inactive / sentinel deleted: resuming crashlog queue\n");
293			dispatch_resume(crashlog_queue);
294			crashmover_state = 0;
295		}
296
297		if (crashlog_sentinel_src != NULL)
298		{
299			dispatch_source_cancel(crashlog_sentinel_src);
300			dispatch_release(crashlog_sentinel_src);
301		}
302
303		crashlog_sentinel_src = NULL;
304
305		close(crashlog_sentinel_fd);
306		crashlog_sentinel_fd = -1;
307		_crashlog_sentinel_init();
308	});
309
310	dispatch_resume(crashlog_sentinel_src);
311	asldebug("Created CrashLog Sentinel: %s\n", path);
312}
313
314static void
315_crashlog_queue_check(void)
316{
317	/*
318	 * Check whether the crashlog queue has been suspended for too long.
319	 * We allow the crashlog quque to be suspended for 60 seconds.
320	 * After that, we start logging again.  This prevents syslogd from
321	 * filling memory due to a suspended queue.  CrashMover really shoud
322	 * take no more than a second or two to finish.
323	 */
324	if (crashmover_state == 0) return;
325	if ((time(NULL) - crashmover_state) <= 60) return;
326
327	asldebug("CrashMover timeout: resuming crashlog queue\n");
328	dispatch_resume(crashlog_queue);
329	crashmover_state = 0;
330
331	/*
332	 * crashlog_sentinel_src should never be NULL, but if
333	 * _crashlog_sentinel_init failed for some strange reason,
334	 * it will be NULL here.
335	 */
336	if (crashlog_sentinel_src != NULL)
337	{
338		dispatch_source_cancel(crashlog_sentinel_src);
339		dispatch_release(crashlog_sentinel_src);
340	}
341
342	crashlog_sentinel_src = NULL;
343
344	close(crashlog_sentinel_fd);
345	crashlog_sentinel_fd = -1;
346	_crashlog_sentinel_init();
347}
348#endif
349
350static void
351_act_dst_close(asl_out_rule_t *r)
352{
353	if (r == NULL) return;
354	if (r->dst == NULL) return;
355	if (r->dst->private == NULL) return;
356
357	if ((r->action == ACTION_ASL_DIR) || (r->action == ACTION_ASL_FILE))
358	{
359		asl_action_store_data_t *sdata = (asl_action_store_data_t *)r->dst->private;
360		if (sdata->store != NULL) asl_file_close(sdata->store);
361		sdata->store = NULL;
362
363		if (sdata->storedata != NULL) fclose(sdata->storedata);
364		sdata->storedata = NULL;
365
366		if (sdata->monitor != NULL)
367		{
368			dispatch_source_cancel(sdata->monitor);
369			dispatch_release(sdata->monitor);
370			sdata->monitor = NULL;
371		}
372	}
373	else if (r->action == ACTION_FILE)
374	{
375		asl_action_file_data_t *fdata = (asl_action_file_data_t *)r->dst->private;
376		if (fdata->fd >= 0) close(fdata->fd);
377		fdata->fd = -1;
378
379		if (fdata->monitor != NULL)
380		{
381			dispatch_source_cancel(fdata->monitor);
382			dispatch_release(fdata->monitor);
383			fdata->monitor = NULL;
384		}
385	}
386}
387
388static uint32_t
389_act_store_file_setup(asl_out_module_t *m, asl_out_rule_t *r)
390{
391	uint32_t status;
392	asl_action_store_data_t *sdata;
393	char dstpath[MAXPATHLEN];
394
395	if (r == NULL) return ASL_STATUS_INVALID_STORE;
396	if (r->dst == NULL) return ASL_STATUS_INVALID_STORE;
397	if (r->dst->private == NULL) return ASL_STATUS_INVALID_STORE;
398
399	sdata = (asl_action_store_data_t *)r->dst->private;
400	if (sdata->store == NULL)
401	{
402		/* create path if necessary */
403		asl_out_mkpath(r);
404
405		int fd = asl_out_dst_file_create_open(r->dst);
406		if (fd < 0)
407		{
408			asldebug("_act_store_file_setup: asl_out_dst_file_create_open failed %d %s\n", errno, strerror(errno));
409			return ASL_STATUS_WRITE_FAILED;
410		}
411		close(fd);
412
413		asl_make_dst_filename(r->dst, dstpath, sizeof(dstpath));
414		status = asl_file_open_write(dstpath, 0, -1, -1, &(sdata->store));
415		if (status != ASL_STATUS_OK) return status;
416
417		sdata->monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fileno(sdata->store->store), DISPATCH_VNODE_DELETE, asl_action_queue);
418		if (sdata->monitor != NULL)
419		{
420			dispatch_source_set_event_handler(sdata->monitor, ^{ _act_dst_close(r); });
421			dispatch_resume(sdata->monitor);
422		}
423
424		status = asl_file_read_set_position(sdata->store, ASL_FILE_POSITION_LAST);
425		if (status != ASL_STATUS_OK)
426		{
427			asldebug("_act_store_file_setup: asl_file_read_set_position failed %d %s\n", status, asl_core_error(status));
428			return status;
429		}
430
431		sdata->next_id = sdata->store->cursor_xid + 1;
432		if (fseek(sdata->store->store, 0, SEEK_END) != 0)
433		{
434			asldebug("_act_store_file_setup: fseek failed %d %s\n", errno, strerror(errno));
435			return ASL_STATUS_WRITE_FAILED;
436		}
437	}
438	else
439	{
440		sdata->next_id++;
441	}
442
443	return ASL_STATUS_OK;
444}
445
446/*
447 * _act_store_dir_setup
448 *
449 * Creates store directory if it does not exist
450 * Creates StoreData file if it does not exist
451 * Reads ASL Message ID from StoreData file
452 * Writes ASL Message ID + 1 to StoreData file
453 * Opens current day file (e.g. "/foo/bar/2012.04.06.asl")
454 */
455static uint32_t
456_act_store_dir_setup(asl_out_module_t *m, asl_out_rule_t *r, time_t tick)
457{
458	struct tm ctm;
459	char *path;
460	struct stat sb;
461	uint64_t xid;
462	int status;
463	mode_t mask;
464	asl_action_store_data_t *sdata;
465
466	if (r == NULL) return ASL_STATUS_INVALID_STORE;
467	if (r->dst == NULL) return ASL_STATUS_INVALID_STORE;
468	if (r->dst->private == NULL) return ASL_STATUS_INVALID_STORE;
469	if (r->dst->path == NULL) return ASL_STATUS_INVALID_STORE;
470
471	sdata = (asl_action_store_data_t *)r->dst->private;
472
473	/* get / set message id from StoreData file */
474	xid = 0;
475
476	if (sdata->storedata == NULL)
477	{
478		memset(&sb, 0, sizeof(struct stat));
479		status = stat(r->dst->path, &sb);
480		if (status == 0)
481		{
482			/* must be a directory */
483			if (!S_ISDIR(sb.st_mode)) return ASL_STATUS_INVALID_STORE;
484		}
485		else if (errno == ENOENT)
486		{
487			/* doesn't exist - create it */
488			mask = umask(S_IWGRP | S_IWOTH);
489			status = mkpath_np(r->dst->path, 0755);
490			if (status == 0) status = chmod(r->dst->path, r->dst->mode);
491			umask(mask);
492
493			if (status != 0) return ASL_STATUS_WRITE_FAILED;
494#if !TARGET_IPHONE_SIMULATOR
495			if (chown(r->dst->path, r->dst->uid[0], r->dst->gid[0]) != 0) return ASL_STATUS_WRITE_FAILED;
496#endif
497		}
498		else
499		{
500			/* Unexpected stat error */
501			return ASL_STATUS_FAILED;
502		}
503
504		path = NULL;
505		asprintf(&path, "%s/%s", r->dst->path, FILE_ASL_STORE_DATA);
506		if (path == NULL) return ASL_STATUS_NO_MEMORY;
507
508		memset(&sb, 0, sizeof(struct stat));
509		status = stat(path, &sb);
510		if (status == 0)
511		{
512			/* StoreData exists: open and read last xid */
513			sdata->storedata = fopen(path, "r+");
514			if (sdata->storedata == NULL)
515			{
516				free(path);
517				return ASL_STATUS_FAILED;
518			}
519
520			if (fread(&xid, sizeof(uint64_t), 1, sdata->storedata) != 1)
521			{
522				free(path);
523				fclose(sdata->storedata);
524				sdata->storedata = NULL;
525				return ASL_STATUS_READ_FAILED;
526			}
527		}
528		else if (errno == ENOENT)
529		{
530			/* StoreData does not exist: create it */
531			sdata->storedata = fopen(path, "w");
532			if (sdata->storedata == NULL)
533			{
534				free(path);
535				return ASL_STATUS_FAILED;
536			}
537
538#if !TARGET_IPHONE_SIMULATOR
539			if (chown(path, r->dst->uid[0], r->dst->gid[0]) != 0)
540			{
541				free(path);
542				return ASL_STATUS_WRITE_FAILED;
543			}
544#endif
545		}
546		else
547		{
548			/* Unexpected stat error */
549			free(path);
550			return ASL_STATUS_FAILED;
551		}
552
553		sdata->monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fileno(sdata->storedata), DISPATCH_VNODE_DELETE, asl_action_queue);
554		if (sdata->monitor != NULL)
555		{
556			dispatch_source_set_event_handler(sdata->monitor, ^{ _act_dst_close(r); });
557			dispatch_resume(sdata->monitor);
558		}
559
560		free(path);
561	}
562	else
563	{
564		rewind(sdata->storedata);
565		if (fread(&xid, sizeof(uint64_t), 1, sdata->storedata) != 1)
566		{
567			fclose(sdata->storedata);
568			sdata->storedata = NULL;
569			return ASL_STATUS_READ_FAILED;
570		}
571	}
572
573	xid = asl_core_ntohq(xid);
574	xid++;
575	sdata->next_id = xid;
576
577	xid = asl_core_htonq(xid);
578	rewind(sdata->storedata);
579	status = fwrite(&xid, sizeof(uint64_t), 1, sdata->storedata);
580	if (status != 1)
581	{
582		fclose(sdata->storedata);
583		sdata->storedata = NULL;
584		return ASL_STATUS_WRITE_FAILED;
585	}
586
587	memset(&ctm, 0, sizeof(struct tm));
588
589	if (localtime_r((const time_t *)&tick, &ctm) == NULL) return ASL_STATUS_FAILED;
590	if ((sdata->store != NULL) && (sdata->p_year == ctm.tm_year) && (sdata->p_month == ctm.tm_mon) && (sdata->p_day == ctm.tm_mday))
591	{
592		return ASL_STATUS_OK;
593	}
594
595	if (sdata->store != NULL) asl_file_close(sdata->store);
596	sdata->store = NULL;
597	free(r->dst->fname);
598	r->dst->fname = NULL;
599
600    r->dst->stamp = tick;
601
602	sdata->p_year = ctm.tm_year;
603	sdata->p_month = ctm.tm_mon;
604	sdata->p_day = ctm.tm_mday;
605
606	asprintf(&(r->dst->fname), "%s/%d.%02d.%02d.asl", r->dst->path, ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
607	if (r->dst->fname == NULL) return ASL_STATUS_NO_MEMORY;
608	mask = umask(0);
609
610	status = ASL_STATUS_OK;
611	if (sdata->store == NULL) {
612#if TARGET_IPHONE_SIMULATOR
613		uid_t uid = -1;
614		gid_t gid = -1;
615#else
616		uid_t uid = r->dst->uid[0];
617		gid_t gid = r->dst->gid[0];
618#endif
619		status = asl_file_open_write(r->dst->fname, (r->dst->mode & 0666), uid, gid, &(sdata->store));
620	}
621	umask(mask);
622
623	if (status != ASL_STATUS_OK) return status;
624
625	if (fseek(sdata->store->store, 0, SEEK_END) != 0) return ASL_STATUS_FAILED;
626
627	return ASL_STATUS_OK;
628}
629
630static void
631_asl_action_store_data_free(asl_action_store_data_t *sdata)
632{
633	if (sdata == NULL) return;
634
635	if (sdata->store != NULL) asl_file_close(sdata->store);
636	sdata->store = NULL;
637
638	if (sdata->storedata != NULL) fclose(sdata->storedata);
639	sdata->storedata = NULL;
640
641	free(sdata);
642}
643
644static void
645_asl_action_file_data_free(asl_action_file_data_t *fdata)
646{
647	if (fdata == NULL) return;
648
649	if (fdata->dup_timer != NULL)
650	{
651		if (fdata->last_count == 0)
652		{
653			/*
654			 * The timer exists, but last_count is zero, so the timer is suspended.
655			 * Sources must not be released in when suspended.
656			 * So we resume it so that we can release it.
657			 */
658			dispatch_resume(fdata->dup_timer);
659		}
660
661		dispatch_release(fdata->dup_timer);
662	}
663
664	free(fdata->last_msg);
665	if (fdata->fd >= 0) close(fdata->fd);
666	free(fdata);
667}
668
669static void
670_asl_action_set_param_data_free(asl_action_set_param_data_t *spdata)
671{
672	if (spdata != NULL) notify_cancel(spdata->token);
673	free(spdata);
674}
675
676static void
677_asl_action_save_failed(const char *where, asl_out_module_t *m, asl_out_rule_t *r, uint32_t status)
678{
679	if (r->dst->flags & MODULE_FLAG_SOFT_WRITE) return;
680
681	r->dst->fails++;
682	asldebug("%s: %s save to %s failed: %s\n", where, m->name, r->dst->path, asl_core_error(status));
683
684	/* disable further activity after multiple failures */
685	if (r->dst->fails > MAX_FAILURES)
686	{
687		char *str = NULL;
688		asprintf(&str, "[Sender syslogd] [Level 3] [PID %u] [Facility syslog] [Message Disabling module %s writes to %s following %u failures (%s)]", global.pid, m->name, r->dst->path, r->dst->fails, asl_core_error(status));
689		internal_log_message(str);
690		free(str);
691
692		if (r->action == ACTION_FILE) _asl_action_file_data_free((asl_action_file_data_t *)r->dst->private);
693		else _asl_action_store_data_free((asl_action_store_data_t *)r->dst->private);
694
695		r->dst->private = NULL;
696		r->action = ACTION_NONE;
697	}
698}
699
700static int
701_act_store_file(asl_out_module_t *m, asl_out_rule_t *r, aslmsg msg)
702{
703	asl_action_store_data_t *sdata;
704	uint32_t status;
705	uint64_t mid;
706
707	if (r == NULL) return ACTION_STATUS_ERROR;
708	if (r->dst == NULL) return ACTION_STATUS_ERROR;
709	if (r->dst->private == NULL) return ACTION_STATUS_ERROR;
710
711	sdata = (asl_action_store_data_t *)r->dst->private;
712
713	/* check dst for file_max & etc */
714	if (r->dst->flags & MODULE_FLAG_ROTATE)
715	{
716		if (asl_out_dst_checkpoint(r->dst, CHECKPOINT_TEST) != 0)
717		{
718			_act_dst_close(r);
719			asl_trigger_aslmanager();
720		}
721	}
722
723	status = _act_store_file_setup(m, r);
724	if (status == ASL_STATUS_OK)
725	{
726		sdata->last_time = time(NULL);
727
728		r->dst->fails = 0;
729		mid = sdata->next_id;
730
731		/* save message to file and update dst size */
732		status = asl_file_save(sdata->store, msg, &mid);
733		if (status == ASL_STATUS_OK) r->dst->size = sdata->store->file_size;
734	}
735
736	if (status != ASL_STATUS_OK)
737	{
738		_asl_action_save_failed("_act_store_file", m, r, status);
739		return ACTION_STATUS_ERROR;
740	}
741
742	return ACTION_STATUS_OK;
743}
744
745static int
746_act_store_dir(asl_out_module_t *m, asl_out_rule_t *r, aslmsg msg)
747{
748	asl_action_store_data_t *sdata;
749	uint32_t status;
750	uint64_t mid;
751	const char *val;
752	time_t tick;
753
754	if (r == NULL) return ACTION_STATUS_ERROR;
755	if (r->dst == NULL) return ACTION_STATUS_ERROR;
756	if (r->dst->private == NULL) return ACTION_STATUS_ERROR;
757
758	sdata = (asl_action_store_data_t *)r->dst->private;
759
760	val = asl_get(msg, ASL_KEY_TIME);
761	if (val == NULL) return ACTION_STATUS_ERROR;
762
763	/* check dst for file_max & etc */
764	if (asl_out_dst_checkpoint(r->dst, CHECKPOINT_TEST) != 0)
765	{
766		_act_dst_close(r);
767		asl_trigger_aslmanager();
768	}
769
770	tick = atol(val);
771
772	status = _act_store_dir_setup(m, r, tick);
773	if (status == ASL_STATUS_OK)
774	{
775		sdata->last_time = time(NULL);
776
777		r->dst->fails = 0;
778		mid = sdata->next_id;
779		status = asl_file_save(sdata->store, msg, &mid);
780		if (status == ASL_STATUS_OK) r->dst->size = sdata->store->file_size;
781	}
782
783	if (status != ASL_STATUS_OK)
784	{
785		_asl_action_save_failed("_act_store_dir", m, r, status);
786		return ACTION_STATUS_ERROR;
787	}
788
789	return ACTION_STATUS_OK;
790}
791
792static void
793_act_store_final(asl_out_module_t *m, asl_out_rule_t *r, aslmsg msg)
794{
795	if (r->action == ACTION_ASL_DIR) _act_store_dir(m, r, msg);
796	else _act_store_file(m, r, msg);
797}
798
799/*
800 * Save a message to an ASL format file (ACTION_ASL_FILE)
801 * or to an ASL directory (ACTION_ASL_DIR).
802 */
803static void
804_act_store(asl_out_module_t *m, asl_out_rule_t *r, aslmsg msg)
805{
806	if (r == NULL) return;
807	if (msg == NULL) return;
808	if (m == NULL) return;
809	if ((m->flags & MODULE_FLAG_ENABLED) == 0) return;
810	if (r->dst == NULL) return;
811
812	if (r->dst->flags & MODULE_FLAG_HAS_LOGGED) return;
813	r->dst->flags |= MODULE_FLAG_HAS_LOGGED;
814
815#if TARGET_OS_EMBEDDED
816	if (r->dst->flags & MODULE_FLAG_CRASHLOG)
817	{
818		_crashlog_queue_check();
819		asl_msg_retain((asl_msg_t *)msg);
820		dispatch_async(crashlog_queue, ^{
821			_act_store_final(m, r, msg);
822			asl_msg_release((asl_msg_t *)msg);
823		});
824		return;
825	}
826#endif
827
828	_act_store_final(m, r, msg);
829}
830
831static int
832_send_repeat_msg(asl_out_rule_t *r)
833{
834	asl_action_file_data_t *fdata;
835	char vt[32], *msg;
836	int len, status;
837	time_t now = time(NULL);
838
839	if (r == NULL) return -1;
840	if (r->dst == NULL) return -1;
841	if (r->dst->private == NULL) return -1;
842
843	fdata = (asl_action_file_data_t *)r->dst->private;
844
845	free(fdata->last_msg);
846	fdata->last_msg = NULL;
847
848	if (fdata->last_count == 0) return 0;
849
850	/* stop the timer */
851	dispatch_suspend(fdata->dup_timer);
852
853	memset(vt, 0, sizeof(vt));
854	ctime_r(&now, vt);
855	vt[19] = '\0';
856
857	msg = NULL;
858	asprintf(&msg, "%s --- last message repeated %u time%s ---\n", vt + 4, fdata->last_count, (fdata->last_count == 1) ? "" : "s");
859	fdata->last_count = 0;
860	fdata->last_time = now;
861	if (msg == NULL) return -1;
862
863	if (fdata->fd < 0) fdata->fd = asl_out_dst_file_create_open(r->dst);
864
865	len = strlen(msg);
866	status = write(fdata->fd, msg, len);
867	free(msg);
868
869	if ((status < 0) || (status < len))
870	{
871		asldebug("%s: error writing repeat message (%s): %s\n", MY_ID, r->dst->path, strerror(errno));
872		return -1;
873	}
874
875	return 0;
876}
877
878static int
879_act_file_open(asl_out_module_t *m, asl_out_rule_t *r)
880{
881	asl_action_file_data_t *fdata;
882
883	if (r == NULL) return -1;
884	if (r->dst == NULL) return -1;
885	if (r->dst->private == NULL) return -1;
886
887	fdata = (asl_action_file_data_t *)r->dst->private;
888	if (fdata->fd < 0)
889	{
890		fdata->fd = asl_out_dst_file_create_open(r->dst);
891		if (fdata->fd < 0)
892		{
893			/*
894			 * lazy path creation: create path and retry
895			 * asl_out_dst_file_create_open doesn not create the path
896			 * so we do it here.
897			 */
898			asl_out_mkpath(r);
899			fdata->fd = asl_out_dst_file_create_open(r->dst);
900		}
901
902		if (fdata->fd >= 0)
903		{
904			fdata->monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fdata->fd, DISPATCH_VNODE_DELETE, asl_action_queue);
905			if (fdata->monitor != NULL)
906			{
907				dispatch_source_set_event_handler(fdata->monitor, ^{ _act_dst_close(r); });
908				dispatch_resume(fdata->monitor);
909			}
910		}
911	}
912
913	return fdata->fd;
914}
915
916static void
917_start_cycling()
918{
919	struct timespec midnight;
920	struct tm t;
921	time_t x;
922
923	x = time(NULL);
924
925	if (checkpoint_timer != NULL) return;
926
927	localtime_r(&x, &t);
928
929	t.tm_sec = 0;
930	t.tm_min = 0;
931	t.tm_hour = 0;
932	t.tm_mday++;
933
934	x = mktime(&t);
935	midnight.tv_sec = x;
936	midnight.tv_nsec = 0;
937
938	checkpoint_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, asl_action_queue);
939	dispatch_source_set_timer(checkpoint_timer, dispatch_walltime(&midnight, 0), NSEC_PER_SEC * SEC_PER_DAY, 0);
940	dispatch_source_set_event_handler(checkpoint_timer, ^{ _act_file_checkpoint_all(CHECKPOINT_FORCE); });
941	dispatch_resume(checkpoint_timer);
942}
943
944/* check if a module path (mpath) matches a user path (upath) */
945static bool
946_act_file_equal(const char *mpath, const char *upath)
947{
948	const char *slash;
949
950	/* NULL upath means user wants to match all files */
951	if (upath == NULL) return true;
952
953	if (mpath == NULL) return false;
954
955	/* check for exact match */
956	if (!strcmp(mpath, upath)) return true;
957
958	/* upath may be the last component of mpath */
959	slash = strrchr(mpath, '/');
960	if (slash == NULL) return false;
961
962	if (!strcmp(slash + 1, upath)) return true;
963	return false;
964}
965
966static int
967_act_file_checkpoint(asl_out_module_t *m, const char *path, uint32_t force)
968{
969	asl_out_rule_t *r;
970	int did_checkpoint = 0;
971
972	if (m == NULL) return 0;
973
974
975	for (r = m->ruleset; r != NULL; r = r->next)
976	{
977		if ((r->action == ACTION_FILE) || (r->action == ACTION_ASL_FILE))
978		{
979			if (r->dst->flags & MODULE_FLAG_ROTATE)
980			{
981				if (_act_file_equal(r->dst->path, path))
982				{
983					if (force & CHECKPOINT_CRASH)
984					{
985						if (r->dst->flags & MODULE_FLAG_CRASHLOG)
986						{
987							if (asl_out_dst_checkpoint(r->dst, CHECKPOINT_FORCE) > 0)
988							{
989								did_checkpoint = 1;
990								_act_dst_close(r);
991							}
992						}
993					}
994					else
995					{
996						if (asl_out_dst_checkpoint(r->dst, force) > 0)
997						{
998							did_checkpoint = 1;
999							_act_dst_close(r);
1000						}
1001					}
1002				}
1003			}
1004		}
1005	}
1006
1007	return did_checkpoint;
1008}
1009
1010static int
1011_act_file_checkpoint_all(uint32_t force)
1012{
1013	asl_out_module_t *m;
1014	int did_checkpoint = 0;
1015
1016	for (m = global.asl_out_module; m != NULL; m = m->next)
1017	{
1018		if (_act_file_checkpoint(m, NULL, force) > 0) did_checkpoint = 1;
1019	}
1020
1021	asl_trigger_aslmanager();
1022
1023	return did_checkpoint;
1024}
1025
1026static void
1027_act_file_final(asl_out_module_t *m, asl_out_rule_t *r, aslmsg msg)
1028{
1029	asl_action_file_data_t *fdata;
1030	int is_dup;
1031	uint32_t len, msg_hash = 0;
1032	char *str;
1033	time_t now;
1034
1035	/*
1036	 * If print format is std, bsd, or msg, then skip messages with
1037	 * no ASL_KEY_MSG, or without a value for it.
1038	 */
1039	if (r->dst->flags & MODULE_FLAG_STD_BSD_MSG)
1040	{
1041		const char *msgval = NULL;
1042		if (asl_msg_lookup((asl_msg_t *)msg, ASL_KEY_MSG, &msgval, NULL) != 0) return;
1043		if (msgval == NULL) return;
1044	}
1045
1046	fdata = (asl_action_file_data_t *)r->dst->private;
1047
1048	now = time(NULL);
1049
1050	is_dup = 0;
1051
1052	str = asl_format_message((asl_msg_t *)msg, r->dst->fmt, r->dst->tfmt, ASL_ENCODE_SAFE, &len);
1053
1054	if (r->dst->flags & MODULE_FLAG_COALESCE)
1055	{
1056		if (fdata->dup_timer == NULL)
1057		{
1058			/* create a timer to flush dups on this file */
1059			fdata->dup_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, asl_action_queue);
1060			dispatch_source_set_event_handler(fdata->dup_timer, ^{ _send_repeat_msg(r); });
1061		}
1062
1063		if ((global.bsd_max_dup_time > 0) && (str != NULL) && (fdata->last_msg != NULL))
1064		{
1065			msg_hash = asl_core_string_hash(str + 16, len - 16);
1066			if ((fdata->last_hash == msg_hash) && (!strcmp(fdata->last_msg, str + 16)))
1067			{
1068				if ((now - fdata->last_time) < global.bsd_max_dup_time) is_dup = 1;
1069			}
1070		}
1071	}
1072
1073	if (is_dup == 1)
1074	{
1075		if (fdata->last_count == 0)
1076		{
1077			/* start the timer */
1078			dispatch_source_set_timer(fdata->dup_timer, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * global.bsd_max_dup_time), DISPATCH_TIME_FOREVER, 0);
1079			dispatch_resume(fdata->dup_timer);
1080		}
1081
1082		fdata->last_count++;
1083	}
1084	else
1085	{
1086		if (_act_file_open(m, r) < 0)
1087		{
1088			_asl_action_save_failed("_act_file", m, r, ASL_STATUS_FAILED);
1089			free(str);
1090			return;
1091		}
1092		else
1093		{
1094			r->dst->fails = 0;
1095		}
1096
1097		/*
1098		 * The current message is not a duplicate.  If fdata->last_count > 0
1099		 * we need to write a "last message repeated N times" log entry.
1100		 * _send_repeat_msg will free last_msg and do nothing if
1101		 * last_count == 0, but we test and free here to avoid a function call.
1102		 */
1103		if (fdata->last_count > 0)
1104		{
1105			_send_repeat_msg(r);
1106		}
1107		else
1108		{
1109			free(fdata->last_msg);
1110			fdata->last_msg = NULL;
1111		}
1112
1113		/* check dst for file_max & etc */
1114		if (r->dst->flags & MODULE_FLAG_ROTATE)
1115		{
1116			int ckpt = asl_out_dst_checkpoint(r->dst, CHECKPOINT_TEST);
1117			if (ckpt != 0)
1118			{
1119				_act_dst_close(r);
1120				asl_trigger_aslmanager();
1121
1122				if (_act_file_open(m, r) < 0)
1123				{
1124					_asl_action_save_failed("_act_file", m, r, ASL_STATUS_FAILED);
1125					free(str);
1126					return;
1127				}
1128				else
1129				{
1130					r->dst->fails = 0;
1131				}
1132			}
1133		}
1134
1135		if (str != NULL) fdata->last_msg = strdup(str + 16);
1136
1137		fdata->last_hash = msg_hash;
1138		fdata->last_count = 0;
1139		fdata->last_time = now;
1140
1141		if ((str != NULL) && (len > 1))
1142		{
1143			/* write line to file and update dst size */
1144			size_t bytes = write(fdata->fd, str, len - 1);
1145			if (bytes > 0) r->dst->size += bytes;
1146		}
1147	}
1148
1149	free(str);
1150}
1151
1152static void
1153_act_file(asl_out_module_t *m, asl_out_rule_t *r, aslmsg msg)
1154{
1155	if (r == NULL) return;
1156	if (msg == NULL) return;
1157	if (m == NULL) return;
1158	if ((m->flags & MODULE_FLAG_ENABLED) == 0) return;
1159	if (r->dst == NULL) return;
1160	if (r->dst->private == NULL) return;
1161
1162	if (r->dst->flags & MODULE_FLAG_HAS_LOGGED) return;
1163
1164	r->dst->flags |= MODULE_FLAG_HAS_LOGGED;
1165
1166#if TARGET_OS_EMBEDDED
1167	if (r->dst->flags & MODULE_FLAG_CRASHLOG)
1168	{
1169		_crashlog_queue_check();
1170		asl_msg_retain((asl_msg_t *)msg);
1171		dispatch_async(crashlog_queue, ^{
1172			_act_file_final(m, r, msg);
1173			asl_msg_release((asl_msg_t *)msg);
1174		});
1175		return;
1176	}
1177#endif
1178
1179	_act_file_final(m, r, msg);
1180}
1181
1182static void
1183_act_forward(asl_out_module_t *m, asl_out_rule_t *r, aslmsg msg)
1184{
1185	/* To do: <rdar://problem/6130747> Add a "forward" action to asl.conf */
1186}
1187
1188static void
1189_act_control(asl_out_module_t *m, asl_out_rule_t *r, aslmsg msg)
1190{
1191	const char *p;
1192
1193	if (m == NULL) return;
1194	if (r == NULL) return;
1195	p = asl_get(msg, ASL_KEY_MODULE);
1196
1197	if (r->options == NULL) return;
1198
1199	if (!strcmp(r->options, "enable"))
1200	{
1201		m->flags |= MODULE_FLAG_ENABLED;
1202	}
1203	else if (!strcmp(r->options, "disable"))
1204	{
1205		m->flags &= ~MODULE_FLAG_ENABLED;
1206	}
1207	else if ((!strcmp(r->options, "checkpoint")) || (!strcmp(r->options, "rotate")))
1208	{
1209		_act_file_checkpoint(m, NULL, CHECKPOINT_FORCE);
1210	}
1211}
1212
1213static void
1214_send_to_asl_store(aslmsg msg)
1215{
1216	if ((global.asl_out_module != NULL) && ((global.asl_out_module->flags & MODULE_FLAG_ENABLED) == 0)) return;
1217
1218	if (store_has_logged) return;
1219	store_has_logged = true;
1220
1221	db_save_message(msg);
1222}
1223
1224static int
1225_asl_out_process_message(asl_out_module_t *m, aslmsg msg)
1226{
1227	asl_out_rule_t *r;
1228
1229	if (m == NULL) return 1;
1230	if (msg == NULL) return 1;
1231
1232	/* reset flag bit used for duplicate avoidance */
1233	for (r = m->ruleset; r != NULL; r = r->next)
1234	{
1235		if ((r->action == ACTION_FILE) || (r->action == ACTION_ASL_DIR) || (r->action == ACTION_ASL_FILE))
1236		{
1237			if (r->dst != NULL) r->dst->flags &= MODULE_FLAG_CLEAR_LOGGED;
1238		}
1239	}
1240
1241	for (r = m->ruleset; r != NULL; r = r->next)
1242	{
1243		if (r->query == NULL) continue;
1244
1245		/* ACTION_SET_FILE, ACTION_SET_PLIST, and ACTION_SET_PROF are handled independently  */
1246		if ((r->action == ACTION_SET_FILE) || (r->action == ACTION_SET_PLIST) || (r->action == ACTION_SET_PROF)) continue;
1247
1248		/*
1249		 * ACTION_CLAIM during processing is a filter.  It will only be here if the option "only"
1250		 * was supplied.  In this case we test the message against the query.  If it does not
1251		 * match, we skip the message.
1252		 */
1253		if (r->action == ACTION_CLAIM)
1254		{
1255			if ((asl_msg_cmp(r->query, (asl_msg_t *)msg) != 1)) return 0;
1256		}
1257
1258		if ((asl_msg_cmp(r->query, (asl_msg_t *)msg) == 1))
1259		{
1260			if (r->action == ACTION_NONE) continue;
1261			else if (r->action == ACTION_IGNORE) return 1;
1262			else if (r->action == ACTION_SKIP) return 0;
1263			else if (r->action == ACTION_ASL_STORE) _send_to_asl_store(msg);
1264			else if (r->action == ACTION_ACCESS) _act_access_control(m, r, msg);
1265			else if (r->action == ACTION_NOTIFY) _act_notify(m, r);
1266			else if (r->action == ACTION_BROADCAST) _act_broadcast(m, r, msg);
1267			else if (r->action == ACTION_FORWARD) _act_forward(m, r, msg);
1268			else if (r->action == ACTION_CONTROL) _act_control(m, r, msg);
1269			else if (r->action == ACTION_SET_PARAM) _act_out_set_param(m, r->options, true);
1270			else if ((r->action == ACTION_ASL_FILE) || (r->action == ACTION_ASL_DIR)) _act_store(m, r, msg);
1271			else if (r->action == ACTION_FILE) _act_file(m, r, msg);
1272		}
1273	}
1274
1275	return 0;
1276}
1277
1278void
1279asl_out_message(aslmsg msg)
1280{
1281	OSAtomicIncrement32(&global.asl_queue_count);
1282	asl_msg_retain((asl_msg_t *)msg);
1283
1284	dispatch_async(asl_action_queue, ^{
1285		int ignore = 0;
1286		const char *p;
1287		time_t now = time(NULL);
1288		asl_out_module_t *m = global.asl_out_module;
1289
1290		store_has_logged = false;
1291
1292		p = asl_get(msg, ASL_KEY_MODULE);
1293		if (p == NULL)
1294		{
1295			if ((action_asl_store_count == 0) || (asl_check_option(msg, ASL_OPT_STORE) == 1)) _send_to_asl_store(msg);
1296
1297			ignore = _asl_out_process_message(m, msg);
1298			if (ignore == 0)
1299			{
1300				if (m != NULL) m = m->next;
1301				while (m != NULL)
1302				{
1303					_asl_out_process_message(m, msg);
1304					m = m->next;
1305				}
1306			}
1307		}
1308		else
1309		{
1310			if (m != NULL) m = m->next;
1311			while (m != NULL)
1312			{
1313				if (!strcmp(p, m->name)) _asl_out_process_message(m, msg);
1314				m = m->next;
1315			}
1316		}
1317
1318		asl_msg_release((asl_msg_t *)msg);
1319		OSAtomicDecrement32(&global.asl_queue_count);
1320
1321		if ((now - sweep_time) >= IDLE_CLOSE)
1322		{
1323			_asl_action_close_idle_files(IDLE_CLOSE);
1324			sweep_time = now;
1325		}
1326	});
1327}
1328
1329static char *
1330_asl_action_profile_test(asl_out_module_t *m, asl_out_rule_t *r)
1331{
1332	const char *ident;
1333	asl_msg_t *profile;
1334	bool eval;
1335
1336	/* ident is first message key */
1337	asl_msg_fetch((asl_msg_t *)r->query, 0, &ident, NULL, NULL);
1338	if (ident == NULL)
1339	{
1340		r->action = ACTION_NONE;
1341		return NULL;
1342	}
1343
1344	profile = configuration_profile_to_asl_msg(ident);
1345	eval = (asl_msg_cmp(r->query, profile) == 1);
1346	_act_out_set_param(m, r->options, eval);
1347	asl_msg_release(profile);
1348
1349	return strdup(ident);
1350}
1351
1352static const char *
1353_asl_action_file_test(asl_out_module_t *m, asl_out_rule_t *r)
1354{
1355	const char *path;
1356	struct stat sb;
1357	int status;
1358	bool eval;
1359
1360	/* path is first message key */
1361	asl_msg_fetch((asl_msg_t *)r->query, 0, &path, NULL, NULL);
1362	if (path == NULL)
1363	{
1364		r->action = ACTION_NONE;
1365		return NULL;
1366	}
1367
1368	memset(&sb, 0, sizeof(struct stat));
1369	status = stat(path, &sb);
1370	eval = (status == 0);
1371	_act_out_set_param(m, r->options, eval);
1372
1373	return path;
1374}
1375
1376static void
1377_asl_action_handle_file_change_notification(int t)
1378{
1379	asl_out_module_t *m;
1380	asl_out_rule_t *r;
1381
1382	for (m = global.asl_out_module; m != NULL; m = m->next)
1383	{
1384		for (r = m->ruleset; r != NULL; r = r->next)
1385		{
1386			if (r->action == ACTION_SET_FILE)
1387			{
1388				asl_action_set_param_data_t *spdata = (asl_action_set_param_data_t *)r->private;
1389				if ((spdata != NULL) && (spdata->token == t))
1390				{
1391					_asl_action_file_test(m, r);
1392					return;
1393				}
1394			}
1395			else if (r->action == ACTION_SET_PLIST)
1396			{
1397				asl_action_set_param_data_t *spdata = (asl_action_set_param_data_t *)r->private;
1398				if ((spdata != NULL) && (spdata->token == t))
1399				{
1400					char *str = _asl_action_profile_test(m, r);
1401					free(str);
1402					return;
1403				}
1404			}
1405			else if (r->action == ACTION_SET_PROF)
1406			{
1407				asl_action_set_param_data_t *spdata = (asl_action_set_param_data_t *)r->private;
1408				if ((spdata != NULL) && (spdata->token == t))
1409				{
1410					char *str = _asl_action_profile_test(m, r);
1411					free(str);
1412					return;
1413				}
1414			}
1415		}
1416	}
1417
1418	asl_out_module_free(m);
1419}
1420
1421static void
1422_asl_action_post_process_rule(asl_out_module_t *m, asl_out_rule_t *r)
1423{
1424	if ((m == NULL) || (r == NULL)) return;
1425
1426	if (m != global.asl_out_module)
1427	{
1428		/* check if any previous module has used this destination */
1429		asl_out_module_t *n;
1430		bool search = true;
1431
1432		if ((r->dst != NULL) && (r->dst->path != NULL))
1433		{
1434			for (n = global.asl_out_module; search && (n != NULL) && (n != m); n = n->next)
1435			{
1436				asl_out_rule_t *s;
1437				for (s = n->ruleset; search && (s != NULL); s = s->next)
1438				{
1439					if (s->action == ACTION_OUT_DEST)
1440					{
1441						if ((s->dst != NULL) && (s->dst->path != NULL) && (!strcmp(r->dst->path, s->dst->path)))
1442						{
1443							/* rule r of module m is using previously used dst of rule s of module n */
1444							asl_out_dst_data_release(r->dst);
1445							r->dst = NULL;
1446
1447							if (r->action == ACTION_OUT_DEST)
1448							{
1449								char *str = NULL;
1450								asprintf(&str, "[Sender syslogd] [Level 5] [PID %u] [Message Configuration Notice:\nASL Module \"%s\" sharing output destination \"%s\" with ASL Module \"%s\".\nOutput parameters from ASL Module \"%s\" override any specified in ASL Module \"%s\".] [UID 0] [GID 0] [Facility syslog]", global.pid, m->name, s->dst->path, n->name, n->name, m->name);
1451								internal_log_message(str);
1452								free(str);
1453							}
1454							else
1455							{
1456								r->dst = asl_out_dst_data_retain(s->dst);
1457							}
1458
1459							search = false;
1460						}
1461					}
1462				}
1463			}
1464		}
1465	}
1466
1467	if (r->action == ACTION_SET_PARAM)
1468	{
1469		if (r->query == NULL) _act_out_set_param(m, r->options, true);
1470	}
1471	else if (r->action == ACTION_CLAIM)
1472	{
1473		/* becomes ACTION_SKIP in com.apple.asl config */
1474		if (m != global.asl_out_module)
1475		{
1476			asl_out_rule_t *rule = (asl_out_rule_t *)calloc(1, sizeof(asl_out_rule_t));
1477			if (rule != NULL)
1478			{
1479				char *str = NULL;
1480				asprintf(&str, "[Sender syslogd] [Level 5] [PID %u] [Message Configuration Notice:\nASL Module \"%s\" claims selected messages.\nThose messages may not appear in standard system log files or in the ASL database.] [UID 0] [GID 0] [Facility syslog]", global.pid, m->name);
1481				internal_log_message(str);
1482				free(str);
1483
1484				rule->query = asl_msg_copy(r->query);
1485				rule->action = ACTION_SKIP;
1486				rule->next = global.asl_out_module->ruleset;
1487				global.asl_out_module->ruleset = rule;
1488			}
1489
1490			/*
1491			 * After adding ACTION_SKIP to com.apple.asl module, the claim becomes a no-op in this module
1492			 * UNLESS the claim includes the option "only".  In that case, the claim becomes a filter:
1493			 * any messages that DO NOT match the claim are skipped by this module.
1494			 */
1495			if (r->options == NULL) r->action = ACTION_NONE;
1496			else if (strcmp(r->options, "only") != 0) r->action = ACTION_NONE;
1497		}
1498	}
1499	else if (r->action == ACTION_ASL_STORE)
1500	{
1501		action_asl_store_count++;
1502	}
1503	else if (r->action == ACTION_ASL_DIR)
1504	{
1505		if (r->dst->private == NULL) r->dst->private = (asl_action_store_data_t *)calloc(1, sizeof(asl_action_store_data_t));
1506	}
1507	else if (r->action == ACTION_ASL_FILE)
1508	{
1509		if (r->dst->private == NULL)r->dst->private = (asl_action_store_data_t *)calloc(1, sizeof(asl_action_store_data_t));
1510	}
1511	else if (r->action == ACTION_FILE)
1512	{
1513		if (r->dst->private == NULL) r->dst->private = (asl_action_file_data_t *)calloc(1, sizeof(asl_action_file_data_t));
1514		if (r->dst->private != NULL) ((asl_action_file_data_t *)(r->dst->private))->fd = -1;
1515	}
1516	else if (r->action == ACTION_SET_PLIST)
1517	{
1518		char *ident =_asl_action_profile_test(m, r);
1519		char *notify_key = configuration_profile_create_notification_key(ident);
1520		free(ident);
1521
1522		if (notify_key != NULL)
1523		{
1524			int status, token;
1525			asl_action_set_param_data_t *spdata;
1526
1527			status = notify_register_dispatch(notify_key, &token, asl_action_queue, ^(int t){
1528				_asl_action_handle_file_change_notification(t);
1529			});
1530
1531			free(notify_key);
1532
1533			spdata = (asl_action_set_param_data_t *)calloc(1, sizeof(asl_action_set_param_data_t));
1534			if (spdata == NULL)
1535			{
1536				notify_cancel(token);
1537			}
1538			else
1539			{
1540				spdata->token = token;
1541				r->private = spdata;
1542			}
1543		}
1544	}
1545	else if (r->action == ACTION_SET_PROF)
1546	{
1547		char *ident =_asl_action_profile_test(m, r);
1548		char *notify_key = configuration_profile_create_notification_key(ident);
1549		free(ident);
1550
1551		if (notify_key != NULL)
1552		{
1553			int status, token;
1554			asl_action_set_param_data_t *spdata;
1555
1556			status = notify_register_dispatch(notify_key, &token, asl_action_queue, ^(int t){
1557				_asl_action_handle_file_change_notification(t);
1558			});
1559
1560			free(notify_key);
1561
1562			spdata = (asl_action_set_param_data_t *)calloc(1, sizeof(asl_action_set_param_data_t));
1563			if (spdata == NULL)
1564			{
1565				notify_cancel(token);
1566			}
1567			else
1568			{
1569				spdata->token = token;
1570				r->private = spdata;
1571			}
1572		}
1573	}
1574	else if (r->action == ACTION_SET_FILE)
1575	{
1576		char *notify_key;
1577		const char *path =_asl_action_file_test(m, r);
1578
1579		if (path != NULL)
1580		{
1581			asprintf(&notify_key, "%s%s", NOTIFY_PATH_SERVICE, path);
1582			if (notify_key != NULL)
1583			{
1584				int status, token;
1585				asl_action_set_param_data_t *spdata;
1586
1587				status = notify_register_dispatch(notify_key, &token, asl_action_queue, ^(int t){
1588					_asl_action_handle_file_change_notification(t);
1589				});
1590
1591				free(notify_key);
1592
1593				spdata = (asl_action_set_param_data_t *)calloc(1, sizeof(asl_action_set_param_data_t));
1594				if (spdata == NULL)
1595				{
1596					notify_cancel(token);
1597				}
1598				else
1599				{
1600					spdata->token = token;
1601					r->private = spdata;
1602				}
1603			}
1604		}
1605	}
1606}
1607
1608static void
1609_asl_action_configure()
1610{
1611	asl_out_rule_t *r;
1612	asl_out_module_t *m;
1613	uint32_t flags = 0;
1614
1615	if (global.asl_out_module == NULL) global.asl_out_module = asl_out_module_init();
1616	if (global.asl_out_module == NULL) return;
1617
1618	if (global.debug != 0)
1619	{
1620		FILE *dfp;
1621		if (global.debug_file == NULL) dfp = fopen(_PATH_SYSLOGD_LOG, "a");
1622		else dfp = fopen(global.debug_file, "a");
1623		if (dfp != NULL)
1624		{
1625			for (m = global.asl_out_module; m != NULL; m = m->next)
1626			{
1627				fprintf(dfp, "module: %s%s\n", (m->name == NULL) ? "<unknown>" : m->name, (m->flags & MODULE_FLAG_LOCAL) ? " (local)" : "");
1628				asl_out_module_print(dfp, m);
1629				fprintf(dfp, "\n");
1630			}
1631			fclose(dfp);
1632		}
1633	}
1634
1635	asldebug("%s: init\n", MY_ID);
1636
1637	action_asl_store_count = 0;
1638
1639	for (m = global.asl_out_module; m != NULL; m = m->next)
1640	{
1641		for (r = m->ruleset; r != NULL; r = r->next)
1642		{
1643			_asl_action_post_process_rule(m, r);
1644			if (r->dst != NULL) flags |= (r->dst->flags & (MODULE_FLAG_ROTATE | MODULE_FLAG_CRASHLOG));
1645		}
1646	}
1647
1648	if (global.debug != 0)
1649	{
1650		FILE *dfp;
1651		if (global.debug_file == NULL) dfp = fopen(_PATH_SYSLOGD_LOG, "a");
1652		else dfp = fopen(global.debug_file, "a");
1653		if (dfp != NULL)
1654		{
1655			for (m = global.asl_out_module; m != NULL; m = m->next)
1656			{
1657				fprintf(dfp, "module: %s%s\n", (m->name == NULL) ? "<unknown>" : m->name, (m->flags & MODULE_FLAG_LOCAL) ? " (local)" : "");
1658				asl_out_module_print(dfp, m);
1659				fprintf(dfp, "\n");
1660			}
1661			fclose(dfp);
1662		}
1663	}
1664
1665	sweep_time = time(NULL);
1666
1667	if (flags & MODULE_FLAG_ROTATE)
1668	{
1669		_act_file_checkpoint_all(CHECKPOINT_TEST);
1670		if (checkpoint_timer == NULL) _start_cycling();
1671	}
1672
1673#if TARGET_OS_EMBEDDED
1674	if (flags & MODULE_FLAG_CRASHLOG) _crashlog_sentinel_init();
1675#endif
1676}
1677
1678int
1679asl_action_init(void)
1680{
1681	static dispatch_once_t once;
1682
1683	dispatch_once(&once, ^{
1684		asl_action_queue = dispatch_queue_create("ASL Action Queue", NULL);
1685#if TARGET_OS_EMBEDDED
1686		crashlog_queue = dispatch_queue_create("iOS CrashLog Queue", NULL);
1687		notify_register_dispatch(CRASH_MOVER_WILL_START_NOTIFICATION, &crashmover_token, asl_action_queue, ^(int unused){
1688			if (crashmover_state == 0)
1689			{
1690				asldebug("CrashMover active: suspending crashlog queue and closing files\n");
1691				crashmover_state = time(NULL);
1692				dispatch_suspend(crashlog_queue);
1693				_asl_action_close_idle_files(0);
1694			}
1695		});
1696#endif
1697	});
1698
1699	_asl_action_configure();
1700
1701	return 0;
1702}
1703
1704/*
1705 * Free a module.
1706 */
1707static void
1708_asl_action_free_modules(asl_out_module_t *m)
1709{
1710	asl_out_rule_t *r;
1711	asl_out_module_t *x;
1712
1713	/*
1714	 * asl_common frees a list of modules with asl_out_module_free.
1715	 * This loop frees the private data attached some modules.
1716	 */
1717	for (x = m; x != NULL; x = x->next)
1718	{
1719		for (r = x->ruleset; r != NULL; r = r->next)
1720		{
1721			if ((r->action == ACTION_ASL_FILE) || (r->action == ACTION_ASL_DIR))
1722			{
1723				if (r->dst != NULL)
1724				{
1725					_asl_action_store_data_free((asl_action_store_data_t *)r->dst->private);
1726					r->dst->private = NULL;
1727				}
1728			}
1729			else if (r->action == ACTION_FILE)
1730			{
1731				if (r->dst != NULL)
1732				{
1733					asl_action_file_data_t *fdata = (asl_action_file_data_t *)r->dst->private;
1734					if (fdata != NULL)
1735					{
1736						/* flush repeat message if necessary */
1737						if (fdata->last_count > 0) _send_repeat_msg(r);
1738						_asl_action_file_data_free(fdata);
1739						r->dst->private = NULL;
1740					}
1741				}
1742			}
1743			else if (r->action == ACTION_SET_PLIST)
1744			{
1745				_asl_action_set_param_data_free((asl_action_set_param_data_t *)r->private);
1746			}
1747			else if (r->action == ACTION_SET_PROF)
1748			{
1749				_asl_action_set_param_data_free((asl_action_set_param_data_t *)r->private);
1750			}
1751		}
1752	}
1753
1754	asl_out_module_free(m);
1755}
1756
1757static int
1758_asl_action_close_internal(void)
1759{
1760#if TARGET_OS_EMBEDDED
1761	dispatch_source_cancel(crashlog_sentinel_src);
1762	dispatch_release(crashlog_sentinel_src);
1763	crashlog_sentinel_src = NULL;
1764	close(crashlog_sentinel_fd);
1765	if (crashmover_state != 0)
1766	{
1767		dispatch_resume(crashlog_queue);
1768		crashmover_state = 0;
1769	}
1770
1771	/* wait for the crashlog_queue to flush before _asl_action_free_modules() */
1772	dispatch_sync(crashlog_queue, ^{ crashlog_sentinel_fd = -1; });
1773#endif
1774
1775	_asl_action_free_modules(global.asl_out_module);
1776	global.asl_out_module = NULL;
1777	sweep_time = time(NULL);
1778
1779	return 0;
1780}
1781
1782static void
1783_asl_action_close_idle_files(time_t idle_time)
1784{
1785	asl_out_module_t *m;
1786	time_t now = time(NULL);
1787
1788	for (m = global.asl_out_module; m != NULL; m = m->next)
1789	{
1790		asl_out_rule_t *r;
1791
1792		for (r = m->ruleset; r != NULL; r = r->next)
1793		{
1794			if (idle_time == 0)
1795			{
1796				if ((r->dst != NULL) && (r->dst->flags & MODULE_FLAG_CRASHLOG))
1797				{
1798					_act_dst_close(r);
1799					if (r->action != ACTION_ASL_DIR) asl_out_dst_checkpoint(r->dst, CHECKPOINT_FORCE);
1800				}
1801			}
1802			else if ((r->action == ACTION_ASL_DIR) || (r->action == ACTION_ASL_FILE))
1803			{
1804				if (r->dst != NULL)
1805				{
1806					asl_action_store_data_t *sdata = (asl_action_store_data_t *)r->dst->private;
1807					if ((sdata != NULL) && (sdata->store != NULL) && ((now - sdata->last_time) >= idle_time)) _act_dst_close(r);
1808				}
1809			}
1810			else if (r->action == ACTION_FILE)
1811			{
1812				if (r->dst != NULL)
1813				{
1814					asl_action_file_data_t *fdata = (asl_action_file_data_t *)r->dst->private;
1815					if ((fdata != NULL) && (fdata->fd >= 0) && ((now - fdata->last_time) >= idle_time)) _act_dst_close(r);
1816				}
1817			}
1818		}
1819	}
1820}
1821
1822int
1823asl_action_close(void)
1824{
1825	dispatch_async(asl_action_queue, ^{
1826		_asl_action_close_internal();
1827	});
1828
1829	return 0;
1830}
1831
1832int
1833asl_action_reset(void)
1834{
1835	dispatch_async(asl_action_queue, ^{
1836		_asl_action_close_internal();
1837		asl_action_init();
1838	});
1839
1840	return 0;
1841}
1842
1843asl_out_module_t *
1844_asl_action_module_with_name(const char *name)
1845{
1846	asl_out_module_t *m;
1847
1848	if (global.asl_out_module == NULL) return NULL;
1849	if (name == NULL) return global.asl_out_module;
1850
1851	for (m = global.asl_out_module; m != NULL; m = m->next)
1852	{
1853		if ((m->name != NULL) && (!strcmp(m->name, name))) return m;
1854	}
1855
1856	return NULL;
1857}
1858
1859/*
1860 * called from control_message
1861 * Used to control modules dynamically.
1862 * Line format "@ module param [value ...]"
1863 *
1864 * Note this is synchronous on asl_action queue.
1865 */
1866int
1867asl_action_control_set_param(const char *s)
1868{
1869	__block char **l;
1870	__block char *p;
1871	uint32_t count = 0;
1872
1873	if (s == NULL) return -1;
1874	if (s[0] == '\0') return 0;
1875
1876	/* skip '@' and whitespace */
1877	if (*s == '@') s++;
1878	while ((*s == ' ') || (*s == '\t')) s++;
1879
1880	l = explode(s, " \t");
1881	if (l != NULL) for (count = 0; l[count] != NULL; count++);
1882
1883	/* at least 2 parameters (l[0] = module, l[1] = param) required */
1884	if (count < 2) return -1;
1885
1886	if (global.asl_out_module == NULL)
1887	{
1888		asldebug("asl_action_control_set_param: no modules loaded\n");
1889		return -1;
1890	}
1891
1892	/* create / modify a module */
1893	if ((!strcasecmp(l[1], "define")) && (strcmp(l[0], "*")))
1894	{
1895		p = strdup(s);
1896		if (p == NULL)
1897		{
1898			asldebug("asl_action_control_set_param: memory allocation failed\n");
1899			return -1;
1900		}
1901
1902		dispatch_sync(asl_action_queue, ^{
1903			asl_out_module_t *m;
1904			asl_out_rule_t *r;
1905
1906			/* skip name, whitespace, "define" */
1907			while ((*p != ' ') && (*p != '\t')) p++;
1908			while ((*p == ' ') || (*p == '\t')) p++;
1909			while ((*p != ' ') && (*p != '\t')) p++;
1910
1911			m = _asl_action_module_with_name(l[0]);
1912			if (m == NULL)
1913			{
1914				asl_out_module_t *x;
1915
1916				m = asl_out_module_new(l[0]);
1917				for (x = global.asl_out_module; x->next != NULL; x = x->next);
1918				x->next = m;
1919			}
1920
1921			r = asl_out_module_parse_line(m, p);
1922			if (r != NULL)
1923			{
1924				_asl_action_post_process_rule(m, r);
1925				if ((r->dst != NULL) && (r->dst->flags & MODULE_FLAG_ROTATE))
1926				{
1927					_act_file_checkpoint_all(CHECKPOINT_TEST);
1928					if (checkpoint_timer == NULL) _start_cycling();
1929				}
1930			}
1931		});
1932
1933		free(p);
1934		free_string_list(l);
1935		return 0;
1936	}
1937
1938	dispatch_sync(asl_action_queue, ^{
1939		uint32_t intval;
1940		int do_all = 0;
1941		asl_out_module_t *m;
1942
1943		if (!strcmp(l[0], "*"))
1944		{
1945			do_all = 1;
1946			m = _asl_action_module_with_name(NULL);
1947		}
1948		else
1949		{
1950			m = _asl_action_module_with_name(l[0]);
1951		}
1952
1953		while (m != NULL)
1954		{
1955			if (!strcasecmp(l[1], "enable"))
1956			{
1957				intval = 1;
1958
1959				/* don't do enable for ASL_MODULE_NAME if input name is "*" */
1960				if ((do_all == 0) || (strcmp(m->name, ASL_MODULE_NAME)))
1961				{
1962					/* @ module enable {0|1} */
1963					if (count > 2) intval = atoi(l[2]);
1964
1965					if (intval == 0) m->flags &= ~MODULE_FLAG_ENABLED;
1966					else m->flags |= MODULE_FLAG_ENABLED;
1967				}
1968			}
1969			else if (!strcasecmp(l[1], "checkpoint"))
1970			{
1971				/* @ module checkpoint [file] */
1972				if (count > 2) _act_file_checkpoint(m, l[2], CHECKPOINT_FORCE);
1973				else _act_file_checkpoint(m, NULL, CHECKPOINT_FORCE);
1974			}
1975
1976			if (do_all == 1) m = m->next;
1977			else m = NULL;
1978		}
1979
1980	});
1981
1982	free_string_list(l);
1983	return 0;
1984}
1985
1986int
1987asl_action_file_checkpoint(const char *module, const char *path)
1988{
1989	/* Note this is synchronous on asl_action queue */
1990	dispatch_sync(asl_action_queue, ^{
1991		asl_out_module_t *m = _asl_action_module_with_name(module);
1992		_act_file_checkpoint(m, path, CHECKPOINT_FORCE);
1993	});
1994
1995	return 0;
1996}
1997