1/*
2 * Copyright (c) 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#include <assert.h>
25#include <stdlib.h>
26#include <stdint.h>
27#include <stdbool.h>
28#include <string.h>
29#include <dirent.h>
30#include <errno.h>
31#include <time.h>
32#include <fcntl.h>
33#include <unistd.h>
34#include <sys/param.h>
35#include <sys/stat.h>
36#include <sys/acl.h>
37#include <membership.h>
38#include <xpc/xpc.h>
39#include <TargetConditionals.h>
40#include <configuration_profile.h>
41#include <asl.h>
42#include <asl_core.h>
43#include <asl_msg.h>
44#include "asl_common.h"
45
46#define _PATH_ASL_CONF "/etc/asl.conf"
47#define _PATH_ASL_CONF_DIR "/etc/asl"
48
49#define PATH_VAR_LOG "/var/log/"
50#define PATH_VAR_LOG_LEN 9
51
52#define PATH_LIBRARY_LOGS "/Library/Logs/"
53#define PATH_LIBRARY_LOGS_LEN 14
54
55#if !TARGET_IPHONE_SIMULATOR
56#define _PATH_ASL_CONF_LOCAL_DIR "/usr/local/etc/asl"
57#endif
58
59static const char *asl_out_action_name[] =
60{
61	"none         ",
62	"set          ",
63	"output       ",
64	"ignore       ",
65	"skip         ",
66	"claim        ",
67	"notify       ",
68	"broadcast    ",
69	"access       ",
70	"set          ",
71	"unset        ",
72	"store        ",
73	"asl_file     ",
74	"asl_dir      ",
75	"file         ",
76	"forward      ",
77	"control      ",
78	"set (file)   ",
79	"set (plist)  ",
80	"set (profile)"
81};
82
83#define forever for(;;)
84#define KEYMATCH(S,K) ((strncasecmp(S, K, strlen(K)) == 0))
85
86#define STAMP_STYLE_INVALID -1
87#define STAMP_STYLE_NULL 0
88#define STAMP_STYLE_SEC 1
89#define STAMP_STYLE_SEQ 2
90#define STAMP_STYLE_UTC_OR_LCL 3
91
92asl_msg_t *
93xpc_object_to_asl_msg(xpc_object_t xobj)
94{
95	__block asl_msg_t *out;
96
97	if (xobj == NULL) return NULL;
98	if (xpc_get_type(xobj) != XPC_TYPE_DICTIONARY) return NULL;
99
100	out = asl_msg_new(ASL_TYPE_MSG);
101	xpc_dictionary_apply(xobj, ^bool(const char *key, xpc_object_t xval) {
102		char tmp[64];
103
104		if (xpc_get_type(xval) == XPC_TYPE_NULL)
105		{
106			asl_msg_set_key_val_op(out, key, NULL, 0);
107		}
108		else if (xpc_get_type(xval) == XPC_TYPE_BOOL)
109		{
110			if (xpc_bool_get_value(xval)) asl_msg_set_key_val_op(out, key, "1", 0);
111			else asl_msg_set_key_val_op(out, key, "0", 0);
112		}
113		else if (xpc_get_type(xval) == XPC_TYPE_INT64)
114		{
115			snprintf(tmp, sizeof(tmp), "%lld", xpc_int64_get_value(xval));
116			asl_msg_set_key_val_op(out, key, tmp, 0);
117		}
118		else if (xpc_get_type(xval) == XPC_TYPE_UINT64)
119		{
120			snprintf(tmp, sizeof(tmp), "%llu", xpc_uint64_get_value(xval));
121			asl_msg_set_key_val_op(out, key, tmp, 0);
122		}
123		else if (xpc_get_type(xval) == XPC_TYPE_DOUBLE)
124		{
125			snprintf(tmp, sizeof(tmp), "%f", xpc_double_get_value(xval));
126			asl_msg_set_key_val_op(out, key, tmp, 0);
127		}
128		else if (xpc_get_type(xval) == XPC_TYPE_DATE)
129		{
130			snprintf(tmp, sizeof(tmp), "%lld", xpc_date_get_value(xval));
131			asl_msg_set_key_val_op(out, key, tmp, 0);
132		}
133		else if (xpc_get_type(xval) == XPC_TYPE_DATA)
134		{
135			size_t len = xpc_data_get_length(xval);
136			char *encoded = asl_core_encode_buffer(xpc_data_get_bytes_ptr(xval), len);
137			asl_msg_set_key_val_op(out, key, encoded, 0);
138			free(encoded);
139		}
140		else if (xpc_get_type(xval) == XPC_TYPE_STRING)
141		{
142			asl_msg_set_key_val_op(out, key, xpc_string_get_string_ptr(xval), 0);
143		}
144		else if (xpc_get_type(xval) == XPC_TYPE_UUID)
145		{
146			uuid_string_t us;
147			uuid_unparse(xpc_uuid_get_bytes(xval), us);
148			asl_msg_set_key_val_op(out, key, us, 0);
149		}
150		else if (xpc_get_type(xval) == XPC_TYPE_FD)
151		{
152			/* XPC_TYPE_FD is not supported */
153			asl_msg_set_key_val_op(out, key, "{XPC_TYPE_FD}", 0);
154		}
155		else if (xpc_get_type(xval) == XPC_TYPE_SHMEM)
156		{
157			/* XPC_TYPE_SHMEM is not supported */
158			asl_msg_set_key_val_op(out, key, "{XPC_TYPE_SHMEM}", 0);
159		}
160		else if (xpc_get_type(xval) == XPC_TYPE_ARRAY)
161		{
162			/* XPC_TYPE_ARRAY is not supported */
163			asl_msg_set_key_val_op(out, key, "{XPC_TYPE_ARRAY}", 0);
164		}
165		else if (xpc_get_type(xval) == XPC_TYPE_DICTIONARY)
166		{
167			/* XPC_TYPE_DICTIONARY is not supported */
168			asl_msg_set_key_val_op(out, key, "{XPC_TYPE_DICTIONARY}", 0);
169		}
170		else if (xpc_get_type(xval) == XPC_TYPE_ERROR)
171		{
172			/* XPC_TYPE_ERROR is not supported */
173			asl_msg_set_key_val_op(out, key, "{XPC_TYPE_ERROR}", 0);
174		}
175		else
176		{
177			/* UNKNOWN TYPE */
178			asl_msg_set_key_val_op(out, key, "{XPC_TYPE_???}", 0);
179		}
180
181		return true;
182	});
183
184	return out;
185}
186
187asl_msg_t *
188configuration_profile_to_asl_msg(const char *ident)
189{
190	xpc_object_t xobj = configuration_profile_copy_property_list(ident);
191	asl_msg_t *out = xpc_object_to_asl_msg(xobj);
192	if (xobj != NULL) xpc_release(xobj);
193	return out;
194}
195
196/* strdup + skip leading and trailing whitespace */
197static char *
198_strdup_clean(const char *s)
199{
200	char *out;
201	const char *first, *last;
202	size_t len;
203
204	if (s == NULL) return NULL;
205
206	first = s;
207	while ((*first == ' ') || (*first == '\t')) first++;
208	len = strlen(first);
209	if (len == 0) return NULL;
210
211	last = first + len - 1;
212	while ((len > 0) && ((*last == ' ') || (*last == '\t')))
213	{
214		last--;
215		len--;
216	}
217
218	if (len == 0) return NULL;
219
220	out = malloc(len + 1);
221	if (out == NULL) return NULL;
222
223	memcpy(out, first, len);
224	out[len] = '\0';
225	return out;
226}
227
228static char **
229_insert_string(char *s, char **l, uint32_t x)
230{
231	int i, len;
232
233	if (s == NULL) return l;
234	if (l == NULL)
235	{
236		l = (char **)malloc(2 * sizeof(char *));
237		if (l == NULL) return NULL;
238
239		l[0] = strdup(s);
240		if (l[0] == NULL)
241		{
242			free(l);
243			return NULL;
244		}
245
246		l[1] = NULL;
247		return l;
248	}
249
250	for (i = 0; l[i] != NULL; i++);
251
252	 /* len includes the NULL at the end of the list */
253	len = i + 1;
254
255	l = (char **)reallocf(l, (len + 1) * sizeof(char *));
256	if (l == NULL) return NULL;
257
258	if ((x >= (len - 1)) || (x == IndexNull))
259	{
260		l[len - 1] = strdup(s);
261		if (l[len - 1] == NULL)
262		{
263			free(l);
264			return NULL;
265		}
266
267		l[len] = NULL;
268		return l;
269	}
270
271	for (i = len; i > x; i--) l[i] = l[i - 1];
272	l[x] = strdup(s);
273	if (l[x] == NULL) return NULL;
274
275	return l;
276}
277
278char **
279explode(const char *s, const char *delim)
280{
281	char **l = NULL;
282	const char *p;
283	char *t, quote;
284	int i, n;
285
286	if (s == NULL) return NULL;
287
288	quote = '\0';
289
290	p = s;
291	while (p[0] != '\0')
292	{
293		/* scan forward */
294		for (i = 0; p[i] != '\0'; i++)
295		{
296			if (quote == '\0')
297			{
298				/* not inside a quoted string: check for delimiters and quotes */
299				if (strchr(delim, p[i]) != NULL) break;
300				else if (p[i] == '\'') quote = p[i];
301				else if (p[i] == '"') quote = p[i];
302			}
303			else
304			{
305				/* inside a quoted string - look for matching quote */
306				if (p[i] == quote) quote = '\0';
307			}
308		}
309
310		n = i;
311		t = malloc(n + 1);
312		if (t == NULL) return NULL;
313
314		for (i = 0; i < n; i++) t[i] = p[i];
315		t[n] = '\0';
316		l = _insert_string(t, l, IndexNull);
317		free(t);
318		t = NULL;
319		if (p[i] == '\0') return l;
320		if (p[i + 1] == '\0') l = _insert_string("", l, IndexNull);
321		p = p + i + 1;
322	}
323
324	return l;
325}
326
327void
328free_string_list(char **l)
329{
330	int i;
331
332	if (l == NULL) return;
333	for (i = 0; l[i] != NULL; i++) free(l[i]);
334	free(l);
335}
336
337char *
338get_line_from_file(FILE *f)
339{
340	char *s, *out;
341	size_t len;
342
343	out = fgetln(f, &len);
344	if (out == NULL) return NULL;
345	if (len == 0) return NULL;
346
347	s = malloc(len + 1);
348	if (s == NULL) return NULL;
349
350	memcpy(s, out, len);
351
352	if (s[len - 1] != '\n') len++;
353	s[len - 1] = '\0';
354	return s;
355}
356
357char *
358next_word_from_string(char **s)
359{
360	char *a, *p, *e, *out, s0;
361	int quote1, quote2, len;
362
363	if (s == NULL) return NULL;
364	if (*s == NULL) return NULL;
365
366	s0 = **s;
367
368	quote1 = 0;
369	quote2 = 0;
370
371	p = *s;
372
373	/* allow whole word to be contained in quotes */
374	if (*p == '\'')
375	{
376		quote1 = 1;
377		p++;
378	}
379
380	if (*p == '"')
381	{
382		quote2 = 1;
383		p++;
384	}
385
386	a = p;
387	e = p;
388
389	while (*p != '\0')
390	{
391		if (*p == '\\')
392		{
393			p++;
394			e = p;
395
396			if (*p == '\0')
397			{
398				p--;
399				break;
400			}
401
402			p++;
403			e = p;
404			continue;
405		}
406
407		if (*p == '\'')
408		{
409			if (quote1 == 0) quote1 = 1;
410			else quote1 = 0;
411		}
412
413		if (*p == '"')
414		{
415			if (quote2 == 0) quote2 = 1;
416			else quote2 = 0;
417		}
418
419		if (((*p == ' ') || (*p == '\t')) && (quote1 == 0) && (quote2 == 0))
420		{
421			e = p + 1;
422			break;
423		}
424
425		p++;
426		e = p;
427	}
428
429	*s = e;
430
431	len = p - a;
432
433	/* check for quoted string */
434	if (((s0 == '\'') || (s0 == '"')) && (s0 == a[len-1])) len--;
435
436	if (len == 0) return NULL;
437
438	out = malloc(len + 1);
439	if (out == NULL) return NULL;
440
441	memcpy(out, a, len);
442	out[len] = '\0';
443	return out;
444}
445
446static asl_out_dst_data_t *
447_asl_out_dest_for_path(asl_out_module_t *m, const char *path)
448{
449	if (m == NULL) return NULL;
450	if (path == NULL) return NULL;
451
452	while (m != NULL)
453	{
454		asl_out_rule_t *r = m->ruleset;
455		while (r != NULL)
456		{
457			if ((r->action == ACTION_OUT_DEST) && (r->dst != NULL) && (r->dst->path != NULL) && (!strcmp(r->dst->path, path))) return r->dst;
458			r = r->next;
459		}
460
461		m = m->next;
462	}
463
464	return NULL;
465}
466
467/*
468 * Create a directory path.
469 *
470 * mlist provides owner, group, and access mode, which are required for "non-standard"
471 * directories.  Directories for standard paths (/var/log or /Library/Logs) default
472 * to root/admin/0755 if there is no mlist rule for them.
473 */
474static int
475_asl_common_make_dir_path(asl_out_module_t *mlist, uint32_t flags, const char *path)
476{
477	int i;
478	char **path_parts;
479	asl_string_t *processed_path;
480	mode_t mode;
481
482	if (path == NULL) return 0;
483
484	path_parts = explode(path, "/");
485	if (path_parts == NULL) return 0;
486
487	processed_path = asl_string_new(ASL_ENCODE_NONE);
488
489	i = 0;
490	if (path[0] == '/') i = 1;
491
492	for (; path_parts[i] != NULL; i++)
493	{
494		struct stat sb;
495		int status;
496		mode_t mask;
497		asl_out_dst_data_t *dst;
498		char *tmp;
499
500		asl_string_append_char_no_encoding(processed_path, '/');
501		asl_string_append_no_encoding(processed_path, path_parts[i]);
502		tmp = asl_string_bytes(processed_path);
503
504		memset(&sb, 0, sizeof(struct stat));
505		status = lstat(tmp, &sb);
506		if ((status == 0) && S_ISLNK(sb.st_mode))
507		{
508			char real[MAXPATHLEN];
509			if (realpath(tmp, real) == NULL)
510			{
511				asl_string_release(processed_path);
512				free_string_list(path_parts);
513				return -1;
514			}
515
516			memset(&sb, 0, sizeof(struct stat));
517			status = stat(real, &sb);
518		}
519
520		if (status == 0)
521		{
522			if (!S_ISDIR(sb.st_mode))
523			{
524				/* path component is not a directory! */
525				asl_string_release(processed_path);
526				free_string_list(path_parts);
527				return -1;
528			}
529
530			/* exists and is a directory or a link to a directory */
531			continue;
532		}
533		else if (errno != ENOENT)
534		{
535			/* unexpected status from stat() */
536			asl_string_release(processed_path);
537			free_string_list(path_parts);
538			return -1;
539		}
540
541		dst = _asl_out_dest_for_path(mlist, tmp);
542		if ((dst == NULL) && (flags & MODULE_FLAG_NONSTD_DIR))
543		{
544			/* no rule to create a non-standard path component! */
545			asl_string_release(processed_path);
546			free_string_list(path_parts);
547			return -1;
548		}
549
550		mode = 0755;
551		if (dst != NULL)
552		{
553			mode = dst->mode;
554			if (mode == 010000) mode = 0755;
555		}
556
557		mask = umask(0);
558		status = mkdir(tmp, mode);
559		umask(mask);
560
561#if !TARGET_IPHONE_SIMULATOR
562		uid_t u = 0;
563		gid_t g = 80;
564
565		if (dst != NULL)
566		{
567			if (dst->nuid > 0) u = dst->uid[0];
568			if (dst->ngid > 0) g = dst->gid[0];
569		}
570
571		chown(tmp, u, g);
572#endif
573	}
574
575	asl_string_release(processed_path);
576	free_string_list(path_parts);
577
578	return 0;
579}
580
581int
582asl_out_mkpath(asl_out_module_t *mlist, asl_out_rule_t *r)
583{
584	char tmp[MAXPATHLEN], *p;
585	struct stat sb;
586	int status;
587
588	if (r == NULL) return -1;
589	if (r->dst == NULL) return -1;
590	if (r->dst->path == NULL) return -1;
591
592	snprintf(tmp, sizeof(tmp), "%s", r->dst->path);
593
594	if (r->action != ACTION_ASL_DIR)
595	{
596		p = strrchr(tmp, '/');
597		if (p == NULL) return -1;
598		*p = '\0';
599	}
600
601	memset(&sb, 0, sizeof(struct stat));
602	status = stat(tmp, &sb);
603	if (status == 0)
604	{
605		if (S_ISDIR(sb.st_mode)) return 0;
606		return -1;
607	}
608
609	if (errno == ENOENT)
610	{
611		uint32_t dirflag = r->dst->flags & MODULE_FLAG_NONSTD_DIR;
612		status = _asl_common_make_dir_path(mlist, dirflag, tmp);
613		return status;
614	}
615
616	return -1;
617}
618
619void
620asl_make_timestamp(time_t stamp, uint32_t flags, char *buf, size_t len)
621{
622	struct tm t;
623	uint32_t h, m, s;
624
625	if (buf == NULL) return;
626
627	if (flags & MODULE_FLAG_STYLE_UTC)
628	{
629		memset(&t, 0, sizeof(t));
630		gmtime_r(&stamp, &t);
631		snprintf(buf, len, "%d-%02d-%02dT%02d:%02d:%02dZ", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
632	}
633	else if (flags & MODULE_FLAG_STYLE_UTC_B)
634	{
635		memset(&t, 0, sizeof(t));
636		gmtime_r(&stamp, &t);
637		snprintf(buf, len, "%d%02d%02dT%02d%02d%02dZ", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
638	}
639	else if (flags & MODULE_FLAG_STYLE_LCL)
640	{
641		bool neg = false;
642		memset(&t, 0, sizeof(t));
643		localtime_r(&stamp, &t);
644
645		if ((neg = (t.tm_gmtoff < 0))) t.tm_gmtoff *= -1;
646
647		s = t.tm_gmtoff;
648		h = s / 3600;
649		s %= 3600;
650		m = s / 60;
651		s %= 60;
652
653		if (s > 0) snprintf(buf, len, "%d-%02d-%02dT%02d:%02d:%02d%c%u:%02u:%02u", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, neg ? '-' : '+', h, m, s);
654		else if (m > 0) snprintf(buf, len, "%d-%02d-%02dT%02d:%02d:%02d%c%u:%02u", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, neg ? '-' : '+', h, m);
655		else snprintf(buf, len, "%d-%02d-%02dT%02d:%02d:%02d%c%u", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, neg ? '-' : '+', h);
656	}
657	else if (flags & MODULE_FLAG_STYLE_LCL_B)
658	{
659		bool neg = false;
660		memset(&t, 0, sizeof(t));
661		localtime_r(&stamp, &t);
662
663		if ((neg = (t.tm_gmtoff < 0))) t.tm_gmtoff *= -1;
664
665		s = t.tm_gmtoff;
666		h = s / 3600;
667		s %= 3600;
668		m = s / 60;
669		s %= 60;
670
671		if (s > 0) snprintf(buf, len, "%d%02d%02dT%02d%02d%02d%c%02u%02u%02u", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, neg ? '-' : '+', h, m, s);
672		else if (m > 0) snprintf(buf, len, "%d%02d%02dT%02d%02d%02d%c%02u%02u", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, neg ? '-' : '+', h, m);
673		else snprintf(buf, len, "%d%02d%02dT%02d%02d%02d%c%02u", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, neg ? '-' : '+', h);
674	}
675	else
676	{
677		snprintf(buf, len, "%c%lu", STYLE_SEC_PREFIX_CHAR, stamp);
678	}
679}
680
681void
682asl_make_dst_filename(asl_out_dst_data_t *dst, char *buf, size_t len)
683{
684	if (dst == NULL) return;
685	if (buf == NULL) return;
686
687	if (dst->flags & (MODULE_FLAG_BASESTAMP | MODULE_FLAG_TYPE_ASL_DIR))
688	{
689		char tstamp[32];
690		const char *name = dst->path;
691
692		if (dst->flags & MODULE_FLAG_TYPE_ASL_DIR) name = dst->fname;
693
694		if (dst->stamp == 0) dst->stamp = time(NULL);
695		asl_make_timestamp(dst->stamp, dst->flags, tstamp, sizeof(tstamp));
696		snprintf(buf, len, "%s.%s", name, tstamp);
697	}
698	else
699	{
700		snprintf(buf, len, "%s", dst->path);
701	}
702}
703
704int
705asl_check_option(asl_msg_t *msg, const char *opt)
706{
707	const char *p;
708	uint32_t len;
709
710	if (msg == NULL) return 0;
711	if (opt == NULL) return 0;
712
713	len = strlen(opt);
714	if (len == 0) return 0;
715
716	p = asl_msg_get_val_for_key(msg, ASL_KEY_OPTION);
717	if (p == NULL) return 0;
718
719	while (*p != '\0')
720	{
721		while ((*p == ' ') || (*p == '\t') || (*p == ',')) p++;
722		if (*p == '\0') return 0;
723
724		if (strncasecmp(p, opt, len) == 0)
725		{
726			p += len;
727			if ((*p == ' ') || (*p == '\t') || (*p == ',') || (*p == '\0')) return 1;
728		}
729
730		while ((*p != ' ') && (*p != '\t') && (*p != ',') && (*p != '\0')) p++;
731	}
732
733	return 0;
734}
735
736void
737asl_out_dst_data_release(asl_out_dst_data_t *dst)
738{
739	if (dst == NULL) return;
740
741	if (dst->refcount > 0) dst->refcount--;
742	if (dst->refcount > 0) return;
743
744	free(dst->path);
745	free(dst->fname);
746	free(dst->rotate_dir);
747	free(dst->fmt);
748#if !TARGET_IPHONE_SIMULATOR
749	free(dst->uid);
750	free(dst->gid);
751#endif
752	free(dst);
753}
754
755asl_out_dst_data_t *
756asl_out_dst_data_retain(asl_out_dst_data_t *dst)
757{
758	if (dst == NULL) return NULL;
759	dst->refcount++;
760	return dst;
761}
762
763/* set owner, group, mode, and acls for a file */
764int
765asl_out_dst_set_access(int fd, asl_out_dst_data_t *dst)
766{
767#if !TARGET_IPHONE_SIMULATOR
768	uid_t fuid = 0;
769	gid_t fgid = 80;
770#if !TARGET_OS_EMBEDDED
771	int status;
772	acl_t acl;
773	uuid_t uuid;
774	acl_entry_t entry;
775	acl_permset_t perms;
776	uint32_t i;
777#endif
778#endif
779
780	if (dst == NULL) return -1;
781	if (fd < 0) return -1;
782
783#if TARGET_IPHONE_SIMULATOR
784	return fd;
785#else
786
787	if (dst->nuid > 0) fuid = dst->uid[0];
788	if (dst->ngid > 0) fgid = dst->gid[0];
789
790	fchown(fd, fuid, fgid);
791
792#if TARGET_OS_EMBEDDED
793	return fd;
794#else
795	acl = acl_init(1);
796
797	for (i = 0; i < dst->ngid; i++)
798	{
799		if (dst->gid[i] == -2) continue;
800
801		/*
802		 * Don't bother setting group access if this is
803		 * file's group and the file is group-readable.
804		 */
805		if ((dst->gid[i] == fgid) && (dst->mode & 00040)) continue;
806
807		status = mbr_gid_to_uuid(dst->gid[i], uuid);
808		if (status != 0)
809		{
810			dst->gid[i] = -2;
811			continue;
812		}
813
814		status = acl_create_entry_np(&acl, &entry, ACL_FIRST_ENTRY);
815		if (status != 0) goto asl_file_create_return;
816
817		status = acl_set_tag_type(entry, ACL_EXTENDED_ALLOW);
818		if (status != 0) goto asl_file_create_return;
819
820		status = acl_set_qualifier(entry, &uuid);
821		if (status != 0) goto asl_file_create_return;
822
823		status = acl_get_permset(entry, &perms);
824		if (status != 0) goto asl_file_create_return;
825
826		status = acl_add_perm(perms, ACL_READ_DATA);
827		if (status != 0) goto asl_file_create_return;
828	}
829
830	for (i = 0; i < dst->nuid; i++)
831	{
832		if (dst->uid[i] == -2) continue;
833
834		/*
835		 * Don't bother setting user access if this is
836		 * file's owner and the file is owner-readable.
837		 */
838		if ((dst->uid[i] == fuid) && (dst->mode & 00400)) continue;
839
840		status = mbr_uid_to_uuid(dst->uid[i], uuid);
841		if (status != 0)
842		{
843			dst->uid[i] = -2;
844			continue;
845		}
846
847		status = acl_create_entry_np(&acl, &entry, ACL_FIRST_ENTRY);
848		if (status != 0) goto asl_file_create_return;
849
850		status = acl_set_tag_type(entry, ACL_EXTENDED_ALLOW);
851		if (status != 0) goto asl_file_create_return;
852
853		status = acl_set_qualifier(entry, &uuid);
854		if (status != 0) goto asl_file_create_return;
855
856		status = acl_get_permset(entry, &perms);
857		if (status != 0) goto asl_file_create_return;
858
859		status = acl_add_perm(perms, ACL_READ_DATA);
860		if (status != 0) goto asl_file_create_return;
861	}
862
863	status = acl_set_fd(fd, acl);
864	if (status != 0)
865	{
866		close(fd);
867		fd = -1;
868	}
869
870asl_file_create_return:
871
872	acl_free(acl);
873	return fd;
874#endif /* !TARGET_OS_EMBEDDED */
875#endif /* !TARGET_IPHONE_SIMULATOR */
876}
877
878/* create a file with acls */
879int
880asl_out_dst_file_create_open(asl_out_dst_data_t *dst, char **pathp)
881{
882	int fd, status;
883	struct stat sb;
884	char outpath[MAXPATHLEN];
885
886	if (dst == NULL) return -1;
887	if (dst->path == NULL) return -1;
888
889	asl_make_dst_filename(dst, outpath, sizeof(outpath));
890	if (dst->fname != NULL) free(dst->fname);
891
892	dst->fname = strdup(outpath);
893	if (dst->fname == NULL) return -1;
894
895	if (pathp != NULL) *pathp = strdup(outpath);
896
897	memset(&sb, 0, sizeof(struct stat));
898	status = stat(outpath, &sb);
899	if (status == 0)
900	{
901		/* must be a regular file */
902		if (!S_ISREG(sb.st_mode)) return -1;
903
904		/* file exists */
905		fd = open(outpath, O_RDWR | O_APPEND | O_EXCL, 0);
906
907		if (dst->stamp == 0) dst->stamp = sb.st_birthtimespec.tv_sec;
908		if (dst->stamp == 0) dst->stamp = sb.st_mtimespec.tv_sec;
909		dst->size = sb.st_size;
910
911		return fd;
912	}
913	else if (errno != ENOENT)
914	{
915		/* stat error other than non-existant file */
916		return -1;
917	}
918
919	fd = open(outpath, O_RDWR | O_CREAT | O_EXCL, (dst->mode & 00666));
920	if (fd < 0) return -1;
921
922	dst->stamp = time(NULL);
923
924	fd = asl_out_dst_set_access(fd, dst);
925	if (fd < 0) unlink(outpath);
926
927	return fd;
928}
929
930void
931asl_out_module_free(asl_out_module_t *m)
932{
933	asl_out_rule_t *r, *n;
934	asl_out_module_t *x;
935
936	while (m != NULL)
937	{
938		x = m->next;
939
940		/* free name */
941		free(m->name);
942
943		/* free ruleset */
944		r = m->ruleset;
945		while (r != NULL)
946		{
947			n = r->next;
948			if (r->dst != NULL) asl_out_dst_data_release(r->dst);
949
950			if (r->query != NULL) asl_msg_release(r->query);
951			free(r->options);
952			free(r);
953			r = n;
954		}
955
956		free(m);
957		m = x;
958	}
959}
960
961asl_out_module_t *
962asl_out_module_new(const char *name)
963{
964	asl_out_module_t *out = (asl_out_module_t *)calloc(1, sizeof(asl_out_module_t));
965
966	if (out == NULL) return NULL;
967	if (name == NULL) return NULL;
968
969	out->name = strdup(name);
970	if (out->name == NULL)
971	{
972		free(out);
973		return NULL;
974	}
975
976	out->flags = MODULE_FLAG_ENABLED;
977
978	return out;
979}
980
981/* Skip over query */
982static char *
983_asl_out_module_find_action(char *s)
984{
985	char *p;
986
987	p = s;
988	if (p == NULL) return NULL;
989
990	/* Skip command character (?, Q, *, or =) */
991	p++;
992
993	forever
994	{
995		/* Find next [ */
996		while ((*p == ' ') || (*p == '\t')) p++;
997
998		if (*p == '\0') return NULL;
999		if (*p != '[') return p;
1000
1001		/* skip to closing ] */
1002		while (*p != ']')
1003		{
1004			p++;
1005			if (*p == '\\')
1006			{
1007				p++;
1008				if (*p == ']') p++;
1009			}
1010		}
1011
1012		if (*p == ']') p++;
1013	}
1014
1015	/* skip whitespace */
1016	while ((*p == ' ') || (*p == '\t')) p++;
1017
1018	return NULL;
1019}
1020
1021/*
1022 * Parse parameter setting line
1023 *
1024 * = param options
1025 *		evaluated once when module is initialized
1026 *
1027 * = [query] param options
1028 *		evaluated for each message, param set if message matches query
1029 *
1030 * = param [File path]
1031 *		evaluated once when module is initialized
1032 *		evaluated when change notification received for path
1033 *
1034 * = param [Plist path] ...
1035 *		evaluated once when module is initialized
1036 *		evaluated when change notification received for path
1037 *
1038 * = param [Profile name] ...
1039 *		evaluated once when module is initialized
1040 *		evaluated when change notification received for profile
1041 */
1042static asl_out_rule_t *
1043_asl_out_module_parse_set_param(asl_out_module_t *m, char *s)
1044{
1045	char *act, *p, *q;
1046	asl_out_rule_t *out, *rule;
1047
1048	if (m == NULL) return NULL;
1049
1050	out = (asl_out_rule_t *)calloc(1, sizeof(asl_out_rule_t));
1051	if (out == NULL) return NULL;
1052
1053	q = s + 1;
1054	while ((*q == ' ') || (*q == '\'')) q++;
1055	out->action = ACTION_SET_PARAM;
1056
1057	if (*q == '[')
1058	{
1059		/* = [query] param options */
1060		act = _asl_out_module_find_action(s);
1061		if (act == NULL)
1062		{
1063			free(out);
1064			return NULL;
1065		}
1066
1067		out->options = _strdup_clean(act);
1068
1069		p = act - 1;
1070		if (*p == ']') p = act;
1071		*p = '\0';
1072
1073		*s = 'Q';
1074		out->query = asl_msg_from_string(s);
1075		if (out->query == NULL)
1076		{
1077			free(out->options);
1078			free(out);
1079			return NULL;
1080		}
1081	}
1082	else
1083	{
1084		/* = param ... */
1085		p = strchr(s, '[');
1086		if (p == NULL)
1087		{
1088			/* = param options */
1089			out->options = _strdup_clean(q);
1090		}
1091		else
1092		{
1093			/* = param [query] */
1094			if ((!strncmp(p, "[File ", 6)) || (!strncmp(p, "[File\t", 6))) out->action = ACTION_SET_FILE;
1095			else if ((!strncmp(p, "[Plist ", 7)) || (!strncmp(p, "[Plist\t", 7))) out->action = ACTION_SET_PLIST;
1096			else if ((!strncmp(p, "[Profile ", 9)) || (!strncmp(p, "[Profile\t", 9))) out->action = ACTION_SET_PROF;
1097
1098			p--;
1099			*p = '\0';
1100			out->options = _strdup_clean(q);
1101
1102			*p = ' ';
1103			p--;
1104			*p = 'Q';
1105			out->query = asl_msg_from_string(p);
1106			if (out->query == NULL)
1107			{
1108				free(out->options);
1109				free(out);
1110				return NULL;
1111			}
1112		}
1113	}
1114
1115	if (m->ruleset == NULL) m->ruleset = out;
1116	else
1117	{
1118		for (rule = m->ruleset; rule->next != NULL; rule = rule->next);
1119		rule->next = out;
1120	}
1121
1122	return out;
1123}
1124
1125#if !TARGET_IPHONE_SIMULATOR
1126static void
1127_dst_add_uid(asl_out_dst_data_t *dst, char *s)
1128{
1129	int i;
1130	uid_t uid;
1131
1132	if (dst == NULL) return;
1133	if (s == NULL) return;
1134
1135	uid = atoi(s);
1136
1137	for (i = 0 ; i < dst->nuid; i++)
1138	{
1139		if (dst->uid[i] == uid) return;
1140	}
1141
1142	dst->uid = reallocf(dst->uid, (dst->nuid + 1) * sizeof(uid_t));
1143	if (dst->uid == NULL)
1144	{
1145		dst->nuid = 0;
1146		return;
1147	}
1148
1149	dst->uid[dst->nuid++] = uid;
1150}
1151
1152static void
1153_dst_add_gid(asl_out_dst_data_t *dst, char *s)
1154{
1155	int i;
1156	gid_t gid;
1157
1158	if (dst == NULL) return;
1159	if (s == NULL) return;
1160
1161	gid = atoi(s);
1162
1163	for (i = 0 ; i < dst->ngid; i++)
1164	{
1165		if (dst->gid[i] == gid) return;
1166	}
1167
1168	dst->gid = reallocf(dst->gid, (dst->ngid + 1) * sizeof(gid_t));
1169	if (dst->gid == NULL)
1170	{
1171		dst->ngid = 0;
1172		return;
1173	}
1174
1175	dst->gid[dst->ngid++] = gid;
1176}
1177#endif /* !TARGET_IPHONE_SIMULATOR */
1178
1179static char *
1180_dst_format_string(char *s)
1181{
1182	char *fmt;
1183	size_t i, len, n;
1184
1185	if (s == NULL) return NULL;
1186
1187	len = strlen(s);
1188
1189	/* format string can be enclosed by quotes */
1190	if ((len >= 2) && ((s[0] == '\'') || (s[0] == '"')) && (s[len-1] == s[0]))
1191	{
1192		s++;
1193		len -= 2;
1194	}
1195
1196	n = 0;
1197	for (i = 0; i < len; i++) if (s[i] == '\\') n++;
1198
1199	fmt = malloc(1 + len - n);
1200	if (fmt == NULL) return NULL;
1201
1202	for (i = 0, n = 0; i < len; i++) if (s[i] != '\\') fmt[n++] = s[i];
1203	fmt[n] = '\0';
1204	return fmt;
1205}
1206
1207size_t
1208asl_str_to_size(char *s)
1209{
1210	size_t len, n, max;
1211	char x;
1212
1213	if (s == NULL) return 0;
1214
1215	len = strlen(s);
1216	if (len == 0) return 0;
1217
1218	n = 1;
1219	x = s[len - 1];
1220	if (x > 90) x -= 32;
1221	if (x == 'K') n = 1ll << 10;
1222	else if (x == 'M') n = 1ll << 20;
1223	else if (x == 'G') n = 1ll << 30;
1224
1225	max = atoll(s) * n;
1226	return max;
1227}
1228
1229static bool
1230_dst_path_match(const char *newpath, const char *existingpath)
1231{
1232	if (newpath == NULL) return (existingpath == NULL);
1233	if (existingpath == NULL) return false;
1234	if (newpath[0] == '/') return (strcmp(newpath, existingpath) == 0);
1235
1236	const char *trailing = strrchr(existingpath, '/');
1237	if (trailing == NULL) return (strcmp(newpath, existingpath) == 0);
1238	trailing++;
1239	return (strcmp(newpath, trailing) == 0);
1240}
1241
1242static asl_out_dst_data_t *
1243_asl_out_module_parse_dst(asl_out_module_t *m, char *s, mode_t def_mode)
1244{
1245	asl_out_rule_t *out, *rule;
1246	asl_out_dst_data_t *dst;
1247	char *p, *opts, *path;
1248	char **path_parts;
1249	int has_dotdot, recursion_limit;
1250	uint32_t i, flags = 0;
1251
1252	if (m == NULL) return NULL;
1253	if (s == NULL) return NULL;
1254
1255	/* skip whitespace */
1256	while ((*s == ' ') || (*s == '\t')) s++;
1257
1258	opts = s;
1259	path = next_word_from_string(&opts);
1260	if (path == NULL) return NULL;
1261
1262	/*
1263	 * Check path for ".." component (not permitted).
1264	 * Also substitute environment variables.
1265	 */
1266	has_dotdot = 0;
1267	path_parts = explode(path, "/");
1268	asl_string_t *processed_path = asl_string_new(ASL_ENCODE_NONE);
1269	recursion_limit = 5;
1270
1271	while ((recursion_limit > 0) && (path_parts != NULL) && (processed_path != NULL))
1272	{
1273		uint32_t i;
1274		int did_sub = 0;
1275
1276		for (i = 0; path_parts[i] != NULL; i++)
1277		{
1278			if (!strncmp(path_parts[i], "$ENV(", 5))
1279			{
1280				char *p = strchr(path_parts[i], ')');
1281				if (p != NULL) *p = '\0';
1282				char *env_val = getenv(path_parts[i] + 5);
1283				if (env_val != NULL)
1284				{
1285					did_sub = 1;
1286
1287					if (env_val[0] != '/') asl_string_append_char_no_encoding(processed_path, '/');
1288					asl_string_append_no_encoding(processed_path, env_val);
1289				}
1290			}
1291			else
1292			{
1293				if (i == 0)
1294				{
1295					if (path_parts[0][0] != '\0') asl_string_append_no_encoding(processed_path, path_parts[i]);
1296				}
1297				else
1298				{
1299					asl_string_append_char_no_encoding(processed_path, '/');
1300					asl_string_append_no_encoding(processed_path, path_parts[i]);
1301				}
1302			}
1303
1304			if ((has_dotdot == 0) && (!strcmp(path_parts[i], ".."))) has_dotdot = 1;
1305		}
1306
1307		free_string_list(path_parts);
1308		path_parts = NULL;
1309
1310		if ((did_sub == 1) && (has_dotdot == 0))
1311		{
1312			/* substitution might have added a ".." so check the new path */
1313			free(path);
1314			path = asl_string_release_return_bytes(processed_path);
1315			processed_path = asl_string_new(ASL_ENCODE_NONE);
1316			path_parts = explode(path, "/");
1317			recursion_limit--;
1318		}
1319	}
1320
1321	free(path);
1322	free_string_list(path_parts);
1323	path_parts = NULL;
1324
1325	if ((has_dotdot != 0) || (recursion_limit == 0))
1326	{
1327		asl_string_release(processed_path);
1328		return NULL;
1329	}
1330
1331	path = asl_string_release_return_bytes(processed_path);
1332
1333	/* check if there's already a dst for this path */
1334	for (rule = m->ruleset; rule != NULL; rule = rule->next)
1335	{
1336		if (rule->action != ACTION_OUT_DEST) continue;
1337
1338		dst = rule->dst;
1339		if (dst == NULL) continue;
1340
1341		if (_dst_path_match(path, dst->path))
1342		{
1343			free(path);
1344			return dst;
1345		}
1346	}
1347
1348	flags |= MODULE_FLAG_NONSTD_DIR;
1349
1350	if (path[0] != '/')
1351	{
1352		char *t = path;
1353		const char *log_root = "/var/log";
1354
1355#if TARGET_IPHONE_SIMULATOR
1356		log_root = getenv("SIMULATOR_LOG_ROOT");
1357		if (log_root == NULL) log_root = "/tmp/log";
1358#endif
1359
1360		if (!strcmp(m->name, ASL_MODULE_NAME))
1361		{
1362			asprintf(&path, "%s/%s", log_root, t);
1363		}
1364		else
1365		{
1366			asprintf(&path, "%s/module/%s/%s", log_root, m->name, t);
1367		}
1368
1369		free(t);
1370		flags &= ~MODULE_FLAG_NONSTD_DIR;
1371	}
1372	else
1373	{
1374		/*
1375		 * Standard log directories get marked so that syslogd
1376		 * will create them without explicit rules.
1377		 */
1378		if (!strncmp(path, PATH_VAR_LOG, PATH_VAR_LOG_LEN)) flags &= ~MODULE_FLAG_NONSTD_DIR;
1379		else if (!strncmp(path, PATH_LIBRARY_LOGS, PATH_LIBRARY_LOGS_LEN)) flags &= ~MODULE_FLAG_NONSTD_DIR;
1380	}
1381
1382	out = (asl_out_rule_t *)calloc(1, sizeof(asl_out_rule_t));
1383	dst = (asl_out_dst_data_t *)calloc(1, sizeof(asl_out_dst_data_t));
1384	if ((out == NULL) || (dst == NULL))
1385	{
1386		free(path);
1387		free(out);
1388		free(dst);
1389		return NULL;
1390	}
1391
1392	dst->refcount = 1;
1393	dst->path = path;
1394	dst->mode = def_mode;
1395	dst->ttl[LEVEL_ALL] = DEFAULT_TTL;
1396	dst->flags = flags | MODULE_FLAG_COALESCE;
1397
1398	while (NULL != (p = next_word_from_string(&opts)))
1399	{
1400		if (KEYMATCH(p, "mode=")) dst->mode = strtol(p+5, NULL, 0);
1401#if !TARGET_IPHONE_SIMULATOR
1402		else if (KEYMATCH(p, "uid=")) _dst_add_uid(dst, p+4);
1403		else if (KEYMATCH(p, "gid=")) _dst_add_gid(dst, p+4);
1404#endif
1405		else if (KEYMATCH(p, "fmt=")) dst->fmt = _dst_format_string(p+4);
1406		else if (KEYMATCH(p, "format=")) dst->fmt = _dst_format_string(p+7);
1407		else if (KEYMATCH(p, "dest=")) dst->rotate_dir = _strdup_clean(p+5);
1408		else if (KEYMATCH(p, "dst=")) dst->rotate_dir = _strdup_clean(p+4);
1409		else if (KEYMATCH(p, "coalesce="))
1410		{
1411			if (KEYMATCH(p+9, "0")) dst->flags &= ~MODULE_FLAG_COALESCE;
1412			else if (KEYMATCH(p+9, "off")) dst->flags &= ~MODULE_FLAG_COALESCE;
1413			else if (KEYMATCH(p+9, "false")) dst->flags &= ~MODULE_FLAG_COALESCE;
1414		}
1415		else if (KEYMATCH(p, "compress")) dst->flags |= MODULE_FLAG_COMPRESS;
1416		else if (KEYMATCH(p, "extern")) dst->flags |= MODULE_FLAG_EXTERNAL;
1417		else if (KEYMATCH(p, "truncate")) dst->flags |= MODULE_FLAG_TRUNCATE;
1418		else if (KEYMATCH(p, "dir")) dst->flags |= MODULE_FLAG_TYPE_ASL_DIR;
1419		else if (KEYMATCH(p, "soft")) dst->flags |= MODULE_FLAG_SOFT_WRITE;
1420		else if (KEYMATCH(p, "file_max=")) dst->file_max = asl_str_to_size(p+9);
1421		else if (KEYMATCH(p, "all_max=")) dst->all_max = asl_str_to_size(p+8);
1422		else if (KEYMATCH(p, "style=") || KEYMATCH(p, "rotate="))
1423		{
1424			const char *x = p + 6;
1425
1426			if (KEYMATCH(p, "rotate=")) x++;
1427
1428			dst->flags |= MODULE_FLAG_ROTATE;
1429
1430			if (KEYMATCH(x, "sec") || KEYMATCH(x, "seconds"))
1431			{
1432				dst->flags |= MODULE_FLAG_STYLE_SEC;
1433			}
1434			else if (KEYMATCH(x, "utc") || KEYMATCH(x, "date") || KEYMATCH(x, "zulu"))
1435			{
1436				const char *dash = strchr(x, '-');
1437				if ((dash != NULL) && (*(dash + 1) == 'b')) dst->flags |= MODULE_FLAG_STYLE_UTC_B;
1438				else dst->flags |= MODULE_FLAG_STYLE_UTC;
1439			}
1440			else if (KEYMATCH(x, "local") || KEYMATCH(x, "lcl"))
1441			{
1442				const char *dash = strchr(x, '-');
1443				if ((dash != NULL) && (*(dash + 1) == 'b')) dst->flags |= MODULE_FLAG_STYLE_LCL_B;
1444				else dst->flags |= MODULE_FLAG_STYLE_LCL;
1445			}
1446			else if (KEYMATCH(x, "#") || KEYMATCH(x, "seq") || KEYMATCH(x, "sequence"))
1447			{
1448				dst->flags |= MODULE_FLAG_STYLE_SEQ;
1449			}
1450			else
1451			{
1452				dst->flags |= MODULE_FLAG_STYLE_SEC;
1453			}
1454		}
1455		else if (KEYMATCH(p, "rotate")) dst->flags |= MODULE_FLAG_ROTATE;
1456		else if (KEYMATCH(p, "crashlog"))
1457		{
1458			/* crashlog implies rotation */
1459			dst->flags |= MODULE_FLAG_ROTATE;
1460			dst->flags |= MODULE_FLAG_CRASHLOG;
1461			dst->flags |= MODULE_FLAG_BASESTAMP;
1462			dst->flags &= ~MODULE_FLAG_COALESCE;
1463		}
1464		else if (KEYMATCH(p, "basestamp"))
1465		{
1466			dst->flags |= MODULE_FLAG_BASESTAMP;
1467		}
1468		else if (KEYMATCH(p, "ttl"))
1469		{
1470			char *q = p + 3;
1471			if (*q == '=')
1472			{
1473				dst->ttl[LEVEL_ALL] = strtol(p+4, NULL, 0);
1474			}
1475			else if ((*q >= '0') && (*q <= '7') && (*(q+1) == '='))
1476			{
1477				uint32_t x = *q - '0';
1478				dst->ttl[x] = strtol(p+5, NULL, 0);
1479			}
1480		}
1481
1482		free(p);
1483		p = NULL;
1484	}
1485
1486#if TARGET_OS_EMBEDDED
1487	/* check for crashreporter files */
1488	if ((KEYMATCH(dst->path, _PATH_CRASHREPORTER)) || (KEYMATCH(dst->path, _PATH_CRASHREPORTER_MOBILE)))
1489	{
1490		dst->flags |= MODULE_FLAG_ROTATE;
1491		dst->flags |= MODULE_FLAG_CRASHLOG;
1492		dst->flags |= MODULE_FLAG_BASESTAMP;
1493		dst->flags &= ~MODULE_FLAG_COALESCE;
1494	}
1495#endif
1496
1497	/* ttl[LEVEL_ALL] must be max of all level-specific ttls */
1498	for (i = 0; i <= 7; i++) if (dst->ttl[i] > dst->ttl[LEVEL_ALL]) dst->ttl[LEVEL_ALL] = dst->ttl[i];
1499
1500	/* default text file format is "std" */
1501	if (dst->fmt == NULL) dst->fmt = strdup("std");
1502
1503	/* duplicate compression is only possible for std and bsd formats */
1504	if (strcmp(dst->fmt, "std") && strcmp(dst->fmt, "bsd")) dst->flags &= ~MODULE_FLAG_COALESCE;
1505
1506	/* note if format is one of std, bsd, or msg */
1507	if (!strcmp(dst->fmt, "std") || !strcmp(dst->fmt, "bsd") || !strcmp(dst->fmt, "msg")) dst->flags |= MODULE_FLAG_STD_BSD_MSG;
1508
1509	/* MODULE_FLAG_STYLE_SEQ can not be used with MODULE_FLAG_BASESTAMP */
1510	if ((dst->flags & MODULE_FLAG_BASESTAMP) && (dst->flags & MODULE_FLAG_STYLE_SEQ))
1511	{
1512		dst->flags &= ~MODULE_FLAG_STYLE_SEQ;
1513		dst->flags |= MODULE_FLAG_STYLE_SEC;
1514	}
1515
1516	/* set time format for raw output */
1517	if (!strcmp(dst->fmt, "raw")) dst->tfmt = "sec";
1518
1519	/* check for ASL_PLACE_DATABASE_DEFAULT */
1520	if (!strcmp(dst->path, ASL_PLACE_DATABASE_DEFAULT))
1521	{
1522		dst->flags = MODULE_FLAG_TYPE_ASL_DIR;
1523	}
1524
1525	out->action = ACTION_OUT_DEST;
1526	out->dst = dst;
1527
1528	/* dst rules go first */
1529	out->next = m->ruleset;
1530	m->ruleset = out;
1531
1532	return dst;
1533}
1534
1535static asl_out_rule_t *
1536_asl_out_module_parse_query_action(asl_out_module_t *m, char *s)
1537{
1538	char *act, *p;
1539	asl_out_rule_t *out, *rule;
1540
1541	if (m == NULL) return NULL;
1542
1543	out = (asl_out_rule_t *)calloc(1, sizeof(asl_out_rule_t));
1544	if (out == NULL) return NULL;
1545
1546	act = _asl_out_module_find_action(s);
1547	if (act == NULL) return NULL;
1548
1549	/* find whitespace delimiter */
1550	p = strchr(act, ' ');
1551	if (p == NULL) p = strchr(act, '\t');
1552	if (p != NULL) *p = '\0';
1553
1554	if (!strcasecmp(act, "ignore"))               out->action = ACTION_IGNORE;
1555	else if (!strcasecmp(act, "skip"))            out->action = ACTION_SKIP;
1556	else if (!strcasecmp(act, "claim"))           out->action = ACTION_CLAIM;
1557	else if (!strcasecmp(act, "notify"))          out->action = ACTION_NOTIFY;
1558	else if (!strcasecmp(act, "file"))            out->action = ACTION_FILE;
1559	else if (!strcasecmp(act, "asl_file"))        out->action = ACTION_ASL_FILE;
1560	else if (!strcasecmp(act, "directory"))       out->action = ACTION_ASL_DIR;
1561	else if (!strcasecmp(act, "dir"))             out->action = ACTION_ASL_DIR;
1562	else if (!strcasecmp(act, "asl_directory"))   out->action = ACTION_ASL_DIR;
1563	else if (!strcasecmp(act, "asl_dir"))         out->action = ACTION_ASL_DIR;
1564	else if (!strcasecmp(act, "store_dir"))       out->action = ACTION_ASL_DIR;
1565	else if (!strcasecmp(act, "store_directory")) out->action = ACTION_ASL_DIR;
1566	else if (!strcasecmp(act, "control"))		  out->action = ACTION_CONTROL;
1567	else if (!strcasecmp(act, "save"))            out->action = ACTION_ASL_STORE;
1568	else if (!strcasecmp(act, "store"))           out->action = ACTION_ASL_STORE;
1569	else if (!strcasecmp(act, "access"))          out->action = ACTION_ACCESS;
1570	else if (!strcasecmp(act, "set"))             out->action = ACTION_SET_KEY;
1571	else if (!strcasecmp(act, "unset"))           out->action = ACTION_UNSET_KEY;
1572	else if	(!strcmp(m->name, ASL_MODULE_NAME))
1573	{
1574		/* actions only allowed in com.apple.asl */
1575		if (!strcasecmp(act, "broadcast"))   out->action = ACTION_BROADCAST;
1576		else if (!strcasecmp(act, "forward"))     out->action = ACTION_FORWARD;
1577	}
1578
1579	if (out->action == ACTION_NONE)
1580	{
1581		free(out);
1582		return NULL;
1583	}
1584
1585	/* options follow delimited (now zero) */
1586	if (p != NULL)
1587	{
1588		/* skip whitespace */
1589		while ((*p == ' ') || (*p == '\t')) p++;
1590
1591		out->options = _strdup_clean(p+1);
1592
1593		if (out->options == NULL)
1594		{
1595			free(out);
1596			return NULL;
1597		}
1598	}
1599
1600	p = act - 1;
1601
1602	*p = '\0';
1603
1604	if (*s== '*')
1605	{
1606		out->query = asl_msg_new(ASL_TYPE_QUERY);
1607	}
1608	else
1609	{
1610		*s = 'Q';
1611		out->query = asl_msg_from_string(s);
1612	}
1613
1614	if (out->query == NULL)
1615	{
1616		free(out->options);
1617		free(out);
1618		return NULL;
1619	}
1620
1621	/* store /some/path means save to an asl file */
1622	if (out->action == ACTION_ASL_STORE)
1623	{
1624		if (out->options == NULL) out->dst = asl_out_dst_data_retain(_asl_out_module_parse_dst(m, ASL_PLACE_DATABASE_DEFAULT, 0755));
1625		else if (!strncmp(out->options, ASL_PLACE_DATABASE_DEFAULT, strlen(ASL_PLACE_DATABASE_DEFAULT))) out->dst = asl_out_dst_data_retain(_asl_out_module_parse_dst(m, out->options, 0755));
1626		else if (out->options != NULL) out->action = ACTION_ASL_FILE;
1627	}
1628
1629	if ((out->action == ACTION_FILE) || (out->action == ACTION_ASL_FILE) || (out->action == ACTION_ASL_DIR))
1630	{
1631		mode_t def_mode = 0644;
1632		if (out->action == ACTION_ASL_DIR) def_mode = 0755;
1633
1634		out->dst = asl_out_dst_data_retain(_asl_out_module_parse_dst(m, out->options, def_mode));
1635		if (out->dst == NULL)
1636		{
1637			out->action = ACTION_NONE;
1638			return out;
1639		}
1640
1641		/*
1642		 * dst might have been set up by a previous ACTION_OUT_DEST ('>') rule with no mode.
1643		 * If so, mode would be 010000.  Set it now, since we know whether it is a file or dir.
1644		 */
1645		if (out->dst->mode == 010000) out->dst->mode = def_mode;
1646
1647		if ((out->action == ACTION_FILE) && (out->dst != NULL) && (out->dst->fmt != NULL) && (!strcasecmp(out->dst->fmt, "asl")))
1648		{
1649			out->action = ACTION_ASL_FILE;
1650		}
1651
1652		if ((out->action == ACTION_ASL_FILE) && (out->dst != NULL))
1653		{
1654			out->dst->flags |= MODULE_FLAG_TYPE_ASL;
1655		}
1656
1657		if (out->action == ACTION_ASL_DIR)
1658		{
1659			/* coalesce is meaningless for ASL directories */
1660			out->dst->flags &= ~MODULE_FLAG_COALESCE;
1661
1662			/* no compression at this point */
1663			out->dst->flags &= ~MODULE_FLAG_COMPRESS;
1664
1665			out->dst->flags |= MODULE_FLAG_TYPE_ASL_DIR;
1666
1667			/* set style bits for basestamp asl_dirs */
1668			if (((out->dst->flags & MODULE_FLAG_STYLE_BITS) == 0) && (out->dst->flags & MODULE_FLAG_BASESTAMP)) out->dst->flags |= MODULE_FLAG_STYLE_LCL_B;
1669		}
1670
1671		/* only ACTION_FILE and ACTION_ASL_FILE may rotate */
1672		if ((out->action != ACTION_FILE) && (out->action != ACTION_ASL_FILE))
1673		{
1674			out->dst->flags &= ~MODULE_FLAG_ROTATE;
1675		}
1676
1677#if !TARGET_IPHONE_SIMULATOR
1678		if (out->dst->nuid == 0) _dst_add_uid(out->dst, "0");
1679		if (out->dst->ngid == 0) _dst_add_gid(out->dst, "80");
1680#endif
1681	}
1682
1683	if (m->ruleset == NULL) m->ruleset = out;
1684	else
1685	{
1686		for (rule = m->ruleset; rule->next != NULL; rule = rule->next);
1687		rule->next = out;
1688	}
1689
1690	return out;
1691}
1692
1693asl_out_rule_t *
1694asl_out_module_parse_line(asl_out_module_t *m, char *s)
1695{
1696	while ((*s == ' ') || (*s == '\t')) s++;
1697
1698	if ((*s == 'Q') || (*s == '?') || (*s == '*'))
1699	{
1700		return _asl_out_module_parse_query_action(m, s);
1701	}
1702	else if (*s == '=')
1703	{
1704		return _asl_out_module_parse_set_param(m, s);
1705	}
1706	else if (*s == '>')
1707	{
1708		_asl_out_module_parse_dst(m, s + 1, 010000);
1709	}
1710
1711	return NULL;
1712}
1713
1714asl_out_module_t *
1715asl_out_module_init_from_file(const char *name, FILE *f)
1716{
1717	asl_out_module_t *out;
1718	char *line;
1719
1720	if (f == NULL) return NULL;
1721
1722	out = asl_out_module_new(name);
1723	if (out == NULL) return NULL;
1724
1725	/* read and parse config file */
1726	while (NULL != (line = get_line_from_file(f)))
1727	{
1728		asl_out_module_parse_line(out, line);
1729		free(line);
1730	}
1731
1732	return out;
1733}
1734
1735static asl_out_module_t *
1736_asl_out_module_find(asl_out_module_t *list, const char *name)
1737{
1738	asl_out_module_t *x;
1739
1740	if (list == NULL) return NULL;
1741	if (name == NULL) return NULL;
1742
1743	for (x = list; x != NULL; x = x->next)
1744	{
1745		if ((x->name != NULL) && (!strcmp(x->name, name))) return x;
1746	}
1747
1748	return NULL;
1749}
1750
1751static void
1752_asl_out_module_read_and_merge_dir(asl_out_module_t **list, const char *path, uint32_t flags)
1753{
1754	DIR *d;
1755	struct dirent *ent;
1756	FILE *f;
1757	asl_out_module_t *last, *x;
1758
1759	if (list == NULL) return;
1760	if (path == NULL) return;
1761
1762	last = *list;
1763	if (last != NULL)
1764	{
1765		while (last->next != NULL) last = last->next;
1766	}
1767
1768	d = opendir(path);
1769	if (d != NULL)
1770	{
1771		while (NULL != (ent = readdir(d)))
1772		{
1773			if ((ent->d_name != NULL) && (ent->d_name[0] != '.'))
1774			{
1775				/* merge: skip this file if we already have a module with this name */
1776				if (_asl_out_module_find(*list, ent->d_name) != NULL) continue;
1777
1778				char tmp[MAXPATHLEN];
1779				snprintf(tmp, sizeof(tmp), "%s/%s", path, ent->d_name);
1780				f = fopen(tmp, "r");
1781				if (f != NULL)
1782				{
1783					x = asl_out_module_init_from_file(ent->d_name, f);
1784					fclose(f);
1785
1786					if (x != NULL)
1787					{
1788						x->flags |= flags;
1789
1790						if (!strcmp(ent->d_name, ASL_MODULE_NAME))
1791						{
1792							/* com.apple.asl goes at the head of the list */
1793							x->next = *list;
1794							*list = x;
1795							if (last == NULL) last = *list;
1796						}
1797						else if (*list == NULL)
1798						{
1799							*list = x;
1800							last = *list;
1801						}
1802						else
1803						{
1804							last->next = x;
1805							last = x;
1806						}
1807					}
1808				}
1809			}
1810		}
1811
1812		closedir(d);
1813	}
1814}
1815
1816asl_out_module_t *
1817asl_out_module_init(void)
1818{
1819	asl_out_module_t *out = NULL;
1820
1821#if TARGET_IPHONE_SIMULATOR
1822	char *sim_root_path, *sim_resources_path;
1823	char *asl_conf, *asl_conf_dir, *asl_conf_local_dir;
1824
1825	sim_root_path = getenv("IPHONE_SIMULATOR_ROOT");
1826	assert(sim_root_path);
1827
1828	sim_resources_path = getenv("IPHONE_SHARED_RESOURCES_DIRECTORY");
1829	assert(sim_resources_path);
1830
1831	asprintf(&asl_conf, "%s%s", sim_root_path, _PATH_ASL_CONF);
1832	asprintf(&asl_conf_dir, "%s%s", sim_root_path, _PATH_ASL_CONF_DIR);
1833	asprintf(&asl_conf_local_dir, "%s%s", sim_resources_path, _PATH_ASL_CONF_DIR);
1834
1835	_asl_out_module_read_and_merge_dir(&out, asl_conf_local_dir, MODULE_FLAG_LOCAL);
1836	free(asl_conf_local_dir);
1837
1838	_asl_out_module_read_and_merge_dir(&out, asl_conf_dir, 0);
1839	free(asl_conf_dir);
1840#else
1841	_asl_out_module_read_and_merge_dir(&out, _PATH_ASL_CONF_LOCAL_DIR, MODULE_FLAG_LOCAL);
1842	_asl_out_module_read_and_merge_dir(&out, _PATH_ASL_CONF_DIR, 0);
1843#endif
1844
1845	if (_asl_out_module_find(out, ASL_MODULE_NAME) == NULL)
1846	{
1847		/* system just has old-style /etc/asl.conf */
1848#if TARGET_IPHONE_SIMULATOR
1849		FILE *f = fopen(asl_conf, "r");
1850		free(asl_conf);
1851#else
1852		FILE *f = fopen(_PATH_ASL_CONF, "r");
1853#endif
1854		if (f != NULL)
1855		{
1856			asl_out_module_t *x = asl_out_module_init_from_file(ASL_MODULE_NAME, f);
1857			fclose(f);
1858			if (x != NULL)
1859			{
1860				x->next = out;
1861				out = x;
1862			}
1863		}
1864	}
1865
1866	return out;
1867}
1868
1869/*
1870 * Print rule
1871 */
1872char *
1873asl_out_module_rule_to_string(asl_out_rule_t *r)
1874{
1875	uint32_t len;
1876	char *str, *out;
1877
1878	if (r == NULL)
1879	{
1880		asprintf(&out, "NULL rule");
1881		return out;
1882	}
1883
1884	str = asl_msg_to_string(r->query, &len);
1885
1886	asprintf(&out, "  %s%s%s%s%s",
1887			 asl_out_action_name[r->action],
1888			 (r->query == NULL) ? "" : " ",
1889			 (r->query == NULL) ? "" : str,
1890			 (r->options == NULL) ? "" : " ",
1891			 (r->options == NULL) ? "" : r->options);
1892
1893	free(str);
1894	return out;
1895}
1896
1897/*
1898 * Print module
1899 */
1900void
1901asl_out_module_print(FILE *f, asl_out_module_t *m)
1902{
1903	asl_out_rule_t *r, *n;
1904	asl_out_dst_data_t *o;
1905	uint32_t i, ttlnset;
1906
1907	n = NULL;
1908	for (r = m->ruleset; r != NULL; r = n)
1909	{
1910		uint32_t len;
1911		char *str = asl_msg_to_string(r->query, &len);
1912
1913		fprintf(f, "  %s", asl_out_action_name[r->action]);
1914		if (r->query != NULL) fprintf(f, " %s", str);
1915		if (r->options != NULL) fprintf(f, " %s", r->options);
1916		if (r->action == ACTION_OUT_DEST)
1917		{
1918			o = r->dst;
1919			if (o == NULL)
1920			{
1921				fprintf(f, "  data: NULL");
1922			}
1923			else
1924			{
1925				fprintf(f, "%s\n", o->path);
1926				fprintf(f, "    rules: %u\n", o->refcount - 1);
1927				fprintf(f, "    dest: %s\n", (o->rotate_dir == NULL) ? "(none)" : o->rotate_dir);
1928				fprintf(f, "    format: %s\n", (o->fmt == NULL) ? "std" : o->fmt);
1929				fprintf(f, "    time_format: %s\n", (o->tfmt == NULL) ? "lcl" : o->tfmt);
1930				fprintf(f, "    flags: 0x%08x", o->flags);
1931				if (o->flags != 0)
1932				{
1933					char c = '(';
1934					fprintf(f, " ");
1935					if (o->flags & MODULE_FLAG_ENABLED)
1936					{
1937						fprintf(f, "%cenabled", c);
1938						c = ' ';
1939					}
1940					if (o->flags & MODULE_FLAG_SOFT_WRITE)
1941					{
1942						fprintf(f, "%csoft", c);
1943						c = ' ';
1944					}
1945					if (o->flags & MODULE_FLAG_ROTATE)
1946					{
1947						fprintf(f, "%crotate", c);
1948						c = ' ';
1949					}
1950					if (o->flags & MODULE_FLAG_COALESCE)
1951					{
1952						fprintf(f, "%ccoalesce", c);
1953						c = ' ';
1954					}
1955					if (o->flags & MODULE_FLAG_COMPRESS)
1956					{
1957						fprintf(f, "%ccompress", c);
1958						c = ' ';
1959					}
1960					if (o->flags & MODULE_FLAG_STYLE_SEC)
1961					{
1962						fprintf(f, "%cseconds", c);
1963						c = ' ';
1964					}
1965					if (o->flags & MODULE_FLAG_STYLE_SEQ)
1966					{
1967						fprintf(f, "%csequence", c);
1968						c = ' ';
1969					}
1970					if (o->flags & MODULE_FLAG_STYLE_UTC)
1971					{
1972						fprintf(f, "%cutc", c);
1973						c = ' ';
1974					}
1975					if (o->flags & MODULE_FLAG_STYLE_UTC_B)
1976					{
1977						fprintf(f, "%cutc-basic", c);
1978						c = ' ';
1979					}
1980					if (o->flags & MODULE_FLAG_STYLE_LCL)
1981					{
1982						fprintf(f, "%clocal", c);
1983						c = ' ';
1984					}
1985					if (o->flags & MODULE_FLAG_STYLE_LCL_B)
1986					{
1987						fprintf(f, "%clocal-basic", c);
1988						c = ' ';
1989					}
1990					if (o->flags & MODULE_FLAG_BASESTAMP)
1991					{
1992						fprintf(f, "%cbasestamp", c);
1993						c = ' ';
1994					}
1995					if (o->flags & MODULE_FLAG_NONSTD_DIR)
1996					{
1997						fprintf(f, "%cnon-std_dir", c);
1998						c = ' ';
1999					}
2000					if (o->flags & MODULE_FLAG_EXTERNAL)
2001					{
2002						fprintf(f, "%cexternal", c);
2003						c = ' ';
2004					}
2005					if (o->flags & MODULE_FLAG_CRASHLOG)
2006					{
2007						fprintf(f, "%ccrashlog", c);
2008						c = ' ';
2009					}
2010					if (o->flags & MODULE_FLAG_TYPE_ASL)
2011					{
2012						fprintf(f, "%casl_file", c);
2013						c = ' ';
2014					}
2015					if (o->flags & MODULE_FLAG_TYPE_ASL_DIR)
2016					{
2017						fprintf(f, "%casl_directory", c);
2018						c = ' ';
2019					}
2020					fprintf(f, ")");
2021				}
2022				fprintf(f, "\n");
2023
2024				fprintf(f, "    ttl: %u", o->ttl[LEVEL_ALL]);
2025				ttlnset = 0;
2026				for (i = 0; (i <= 7) & (ttlnset == 0); i++) if (o->ttl[i] != 0) ttlnset = 1;
2027				if (ttlnset != 0) for (i = 0; i <= 7; i++) printf(" [%d %d]", i, (o->ttl[i] == 0) ? o->ttl[LEVEL_ALL] : o->ttl[i]);
2028				fprintf(f, "\n");
2029
2030				fprintf(f, "    mode: 0%o\n", o->mode);
2031				fprintf(f, "    file_max: %lu\n", o->file_max);
2032				fprintf(f, "    all_max: %lu\n", o->all_max);
2033#if !TARGET_IPHONE_SIMULATOR
2034				fprintf(f, "    uid:");
2035				for (i = 0; i < o->nuid; i++) fprintf(f, " %d", o->uid[i]);
2036				fprintf(f, "\n");
2037				fprintf(f, "    gid:");
2038				for (i = 0; i < o->ngid; i++) fprintf(f, " %d", o->gid[i]);
2039#endif
2040			}
2041		}
2042
2043		fprintf(f, "\n");
2044		n = r->next;
2045
2046		free(str);
2047	}
2048}
2049
2050void
2051asl_out_file_list_free(asl_out_file_list_t *l)
2052{
2053	asl_out_file_list_t *n;
2054
2055	if (l == NULL) return;
2056
2057	while (l != NULL)
2058	{
2059		free(l->name);
2060		n = l->next;
2061		free(l);
2062		l = n;
2063	}
2064}
2065
2066/*
2067 * Checks input name for the form base[.stamp][.gz]
2068 * name == base is allowed if src is true.
2069 * base.gz is not allowed.
2070 * Output parameter stamp must be freed by caller.
2071 */
2072bool
2073_check_file_name(const char *name, const char *base, bool src, char **stamp)
2074{
2075	size_t baselen, nparts;
2076	const char *p, *q, *part[2];
2077	bool isgz = false;
2078
2079	if (name == NULL) return false;
2080	if (base == NULL) return false;
2081
2082	baselen = strlen(base);
2083	if (baselen == 0) return false;
2084
2085	if (stamp != NULL) *stamp = NULL;
2086
2087	if (strncmp(name, base, baselen)) return false;
2088
2089	p = name + baselen;
2090
2091	/* name == base not allowed (it's the "active" file) */
2092	if (*p == '\0') return false;
2093
2094	/* name must be base.something */
2095	if (*p != '.') return false;
2096
2097	/* maximum of 2 parts (stamp and gz) */
2098	nparts = 0;
2099	for (q = p; *q != '\0'; q++)
2100	{
2101		if (*q == '.')
2102		{
2103			if (nparts == 2) return false;
2104			part[nparts++] = q + 1;
2105		}
2106	}
2107
2108	if (nparts == 0) return false;
2109
2110	isgz = strcmp(part[nparts - 1], "gz") == 0;
2111
2112	/* no compressed files in src */
2113	if (src && isgz) return false;
2114
2115	/* expecting base.stamp or base.stamp.gz */
2116
2117	if (nparts == 1)
2118	{
2119		/* compressed files must have a stamp (base.gz is not allowed) */
2120		if (isgz) return false;
2121
2122		/* got base.stamp */
2123		if (stamp != NULL) *stamp = strdup(part[0]);
2124		return true;
2125	}
2126
2127	/* expecting base.stamp.gz */
2128	if (!isgz) return false;
2129
2130	/* got base.stamp.gz */
2131	if (stamp != NULL)
2132	{
2133		*stamp = strdup(part[0]);
2134		char *x = strchr(*stamp, '.');
2135		if (x != NULL) *x = '\0';
2136	}
2137
2138	return true;
2139}
2140
2141/*
2142 * Find files in a directory (dir) that all have a common prefix (base).
2143 * Bits in flags further control the search.
2144 *
2145 * MODULE_FLAG_STYLE_SEQ means a numeric sequence number is expected, although not required.
2146 * E.g. foo.log foo.log.0
2147 *
2148 * MODULE_FLAG_STYLE_SEC also means a numeric sequence number is required following an 'T' character.
2149 * The numeric value is the file's timestamp in seconds.  E.g foo.log.T1335200452
2150 *
2151 * MODULE_FLAG_STYLE_UTC requires a date/time component as the file's timestamp.
2152 * E.g. foo.2012-04-06T15:30:00Z
2153 *
2154 * MODULE_FLAG_STYLE_UTC_B requires a date/time component as the file's timestamp.
2155 * E.g. foo.20120406T153000Z
2156 *
2157 * MODULE_FLAG_STYLE_LCL requires a date/time component as the file's timestamp.
2158 * E.g. foo.2012-04-06T15:30:00-7
2159 *
2160 * MODULE_FLAG_STYLE_LCL_B requires a date/time component as the file's timestamp.
2161 * E.g. foo.20120406T153000-07
2162 */
2163int
2164_parse_stamp_style(char *stamp, uint32_t flags, uint32_t *sp, time_t *tp)
2165{
2166	int i, n;
2167	bool digits;
2168	struct tm t;
2169	char zone;
2170	uint32_t h, m, s;
2171	long utc_offset = 0;
2172	time_t ftime = 0;
2173
2174	/* check for NULL (no stamp) */
2175	if (stamp == NULL) return STAMP_STYLE_NULL;
2176
2177	/* check for MODULE_FLAG_STYLE_SEC (foo.T12345678) */
2178	if (stamp[0] == 'T')
2179	{
2180		n = atoi(stamp + 1);
2181		if ((n == 0) && strcmp(stamp + 1, "0")) return STAMP_STYLE_INVALID;
2182		if (tp != NULL) *tp = (time_t)n;
2183
2184		return STAMP_STYLE_SEC;
2185	}
2186
2187	/* check for MODULE_FLAG_STYLE_SEQ (foo.0 or foo.2.gz) */
2188	digits = true;
2189	for (i = 0; digits && (stamp[i] != '\0'); i++) digits = (stamp[i] >= '0') && (stamp[i] <= '9');
2190
2191	if (!digits && (!strcmp(stamp + i, ".gz"))) digits = true;
2192
2193	if (digits)
2194	{
2195		n = atoi(stamp);
2196		if (sp != NULL) *sp = (uint32_t)n;
2197		return STAMP_STYLE_SEQ;
2198	}
2199
2200	/* check for MODULE_FLAG_STYLE_UTC, UTC_B, LCL, or LCL_B */
2201	memset(&t, 0, sizeof(t));
2202	h = m = s = 0;
2203
2204	n = 0;
2205	if ((flags & MODULE_FLAG_STYLE_UTC) || (flags & MODULE_FLAG_STYLE_LCL))
2206	{
2207		n = sscanf(stamp, "%d-%d-%dT%d:%d:%d%c%u:%u:%u", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec, &zone, &h, &m, &s);
2208	}
2209	else if ((flags & MODULE_FLAG_STYLE_UTC_B) || (flags & MODULE_FLAG_STYLE_LCL_B))
2210	{
2211		n = sscanf(stamp, "%4d%2d%2dT%2d%2d%2d%c%2u%2u%2u", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec, &zone, &h, &m, &s);
2212	}
2213	else
2214	{
2215		return STAMP_STYLE_INVALID;
2216	}
2217
2218	if (n < 6) return STAMP_STYLE_INVALID;
2219
2220	if (n == 6)
2221	{
2222		zone = 'J';
2223	}
2224	else if ((zone == '-') || (zone == '+'))
2225	{
2226		if (n >= 8) utc_offset += (3600 * h);
2227		if (n >= 9) utc_offset += (60 * m);
2228		if (n == 10) utc_offset += s;
2229		if (zone == '-') utc_offset *= -1;
2230	}
2231	else if ((zone >= 'A') && (zone <= 'Z'))
2232	{
2233		if (zone < 'J') utc_offset = 3600 * ((zone - 'A') + 1);
2234		else if ((zone >= 'K') && (zone <= 'M')) utc_offset = 3600 * (zone - 'A');
2235		else if (zone <= 'Y') utc_offset = -3600 * ((zone - 'N') + 1);
2236	}
2237	else if ((zone >= 'a') && (zone <= 'z'))
2238	{
2239		if (zone < 'j') utc_offset = 3600 * ((zone - 'a') + 1);
2240		else if ((zone >= 'k') && (zone <= 'm')) utc_offset = 3600 * (zone - 'a');
2241		else if (zone <= 'y') utc_offset = -3600 * ((zone - 'n') + 1);
2242	}
2243	else
2244	{
2245		return STAMP_STYLE_INVALID;
2246	}
2247
2248	t.tm_year -= 1900;
2249	t.tm_mon -= 1;
2250	t.tm_sec += utc_offset;
2251	t.tm_isdst = -1;
2252
2253	if ((zone == 'J') || (zone == 'j')) ftime = mktime(&t);
2254	else ftime = timegm(&t);
2255
2256	if (tp != NULL) *tp = ftime;
2257
2258	return STAMP_STYLE_UTC_OR_LCL;
2259}
2260
2261asl_out_file_list_t *
2262asl_list_log_files(const char *dir, const char *base, bool src, uint32_t flags)
2263{
2264	DIR *d;
2265	struct dirent *ent;
2266	char path[MAXPATHLEN];
2267	uint32_t seq;
2268	time_t ftime;
2269	struct stat sb;
2270	int pstyle, fstyle;
2271	asl_out_file_list_t *out, *x, *y;
2272
2273	if (dir == NULL) return NULL;
2274	if (base == NULL) return NULL;
2275
2276	out = NULL;
2277
2278	d = opendir(dir);
2279	if (d == NULL) return NULL;
2280
2281	while (NULL != (ent = readdir(d)))
2282	{
2283		char *stamp = NULL;
2284		bool check;
2285
2286		if (ent->d_name == NULL) continue;
2287
2288		check = _check_file_name(ent->d_name, base, src, &stamp);
2289		if (!check) continue;
2290
2291		seq = IndexNull;
2292		ftime = 0;
2293
2294		pstyle = _parse_stamp_style(stamp, flags, &seq, &ftime);
2295		free(stamp);
2296
2297		if (pstyle == STAMP_STYLE_INVALID) continue;
2298
2299		fstyle = STAMP_STYLE_NULL;
2300		if (flags & MODULE_FLAG_STYLE_SEC) fstyle = STAMP_STYLE_SEC;
2301		else if (flags & MODULE_FLAG_STYLE_SEQ) fstyle = STAMP_STYLE_SEQ;
2302		else if ((flags & MODULE_FLAG_STYLE_UTC) || (flags & MODULE_FLAG_STYLE_LCL)) fstyle = STAMP_STYLE_UTC_OR_LCL;
2303		else if ((flags & MODULE_FLAG_STYLE_UTC_B) || (flags & MODULE_FLAG_STYLE_LCL_B)) fstyle = STAMP_STYLE_UTC_OR_LCL;
2304
2305		/*
2306		 * accept the file if:
2307		 * style is STAMP_STYLE_NULL (no timestamp)
2308		 * src is true and style is STAMP_STYLE_SEC
2309		 * actual style matches the style implied by the input flags
2310		 */
2311
2312		check = false;
2313		if (pstyle == STAMP_STYLE_NULL) check = true;
2314		if ((pstyle == STAMP_STYLE_SEC) && src) check = true;
2315		if (pstyle == fstyle) check = true;
2316
2317		if (!check) continue;
2318
2319		x = (asl_out_file_list_t *)calloc(1, sizeof(asl_out_file_list_t));
2320		if (x == NULL)
2321		{
2322			asl_out_file_list_free(out);
2323			return NULL;
2324		}
2325
2326		x->name = strdup(ent->d_name);
2327		x->ftime = ftime;
2328		x->seq = seq;
2329
2330		memset(&sb, 0, sizeof(sb));
2331		snprintf(path, sizeof(path), "%s/%s", dir, ent->d_name);
2332		if (stat(path, &sb) == 0)
2333		{
2334			x->size = sb.st_size;
2335			if (pstyle == STAMP_STYLE_SEQ)
2336			{
2337				x->ftime = sb.st_birthtimespec.tv_sec;
2338				if (x->ftime == 0) x->ftime = sb.st_mtimespec.tv_sec;
2339			}
2340		}
2341
2342		if (pstyle == STAMP_STYLE_SEQ)
2343		{
2344			if (out == NULL)
2345			{
2346				out = x;
2347			}
2348			else if ((x->seq == IndexNull) || ((x->seq < out->seq) && (out->seq != IndexNull)))
2349			{
2350				x->next = out;
2351				out->prev = x;
2352				out = x;
2353			}
2354			else
2355			{
2356				for (y = out; y != NULL; y = y->next)
2357				{
2358					if (y->next == NULL)
2359					{
2360						y->next = x;
2361						x->prev = y;
2362						break;
2363					}
2364					else if ((x->seq < y->next->seq) && (y->next->seq != IndexNull))
2365					{
2366						x->next = y->next;
2367						y->next = x;
2368						x->prev = y;
2369						x->next->prev = x;
2370						break;
2371					}
2372				}
2373			}
2374		}
2375		else
2376		{
2377			if (out == NULL)
2378			{
2379				out = x;
2380			}
2381			else if (x->ftime < out->ftime)
2382			{
2383				x->next = out;
2384				out->prev = x;
2385				out = x;
2386			}
2387			else
2388			{
2389				for (y = out; y != NULL; y = y->next)
2390				{
2391					if (y->next == NULL)
2392					{
2393						y->next = x;
2394						x->prev = y;
2395						break;
2396					}
2397					else if (x->ftime < y->next->ftime)
2398					{
2399						x->next = y->next;
2400						y->next = x;
2401						x->prev = y;
2402						x->next->prev = x;
2403						break;
2404					}
2405				}
2406			}
2407		}
2408	}
2409
2410	closedir(d);
2411	return out;
2412}
2413
2414/*
2415 * List the source files for an output asl_out_dst_data_t
2416 */
2417asl_out_file_list_t *
2418asl_list_src_files(asl_out_dst_data_t *dst)
2419{
2420	char *base;
2421	uint32_t flags = MODULE_FLAG_STYLE_SEC;
2422	asl_out_file_list_t *out;
2423
2424	if (dst == NULL) return NULL;
2425	if (dst->path == NULL) return NULL;
2426
2427	/*
2428	 * MODULE_FLAG_EXTERNAL means some process other than syslogd writes the file.
2429	 * We check for its existence, and that it is non-zero in size.
2430	 */
2431	if (dst->flags & MODULE_FLAG_EXTERNAL)
2432	{
2433		struct stat sb;
2434
2435		memset(&sb, 0, sizeof(struct stat));
2436
2437		if (stat(dst->path, &sb) == 0)
2438		{
2439			if (S_ISREG(sb.st_mode) && (sb.st_size != 0))
2440			{
2441				out = (asl_out_file_list_t *)calloc(1, sizeof(asl_out_file_list_t));
2442				if (out != NULL)
2443				{
2444					char *p = strrchr(dst->path, '/');
2445					if (p == NULL) p = dst->path;
2446					else p++;
2447					out->name = strdup(p);
2448					out->ftime = sb.st_birthtimespec.tv_sec;
2449					if (out->ftime == 0) out->ftime = sb.st_mtimespec.tv_sec;
2450					return out;
2451				}
2452			}
2453		}
2454
2455		return NULL;
2456	}
2457
2458	/*
2459	 * Checkpoint / source format may be one of:
2460	 * MODULE_FLAG_STYLE_SEC   (foo.T12345678.log),
2461	 * MODULE_FLAG_STYLE_UTC   (foo.20120-06-24T12:34:56Z.log)
2462	 * MODULE_FLAG_STYLE_UTC_B (foo.201200624T123456Z.log)
2463	 * MODULE_FLAG_STYLE_LCL   (foo.20120-06-24T12:34:56-7.log)
2464	 * MODULE_FLAG_STYLE_LCL_B (foo.201200624T123456-07.log)
2465	 *
2466	 * MODULE_FLAG_STYLE_SEC format is used for sequenced (MODULE_FLAG_STYLE_SEQ) files.
2467	 * aslmanager converts the file names.
2468	 */
2469
2470	if (dst->flags & MODULE_FLAG_STYLE_UTC) flags = MODULE_FLAG_STYLE_UTC;
2471	else if (dst->flags & MODULE_FLAG_STYLE_UTC_B) flags = MODULE_FLAG_STYLE_UTC_B;
2472	else if (dst->flags & MODULE_FLAG_STYLE_LCL) flags = MODULE_FLAG_STYLE_LCL;
2473	else if (dst->flags & MODULE_FLAG_STYLE_LCL_B) flags = MODULE_FLAG_STYLE_LCL_B;
2474
2475	if ((dst->rotate_dir == NULL) && ((dst->flags & MODULE_FLAG_STYLE_SEQ) == 0) && ((dst->flags & MODULE_FLAG_COMPRESS) == 0))
2476	{
2477		/* files do not move to a dest dir, get renamed, or get compressed - nothing to do */
2478		return NULL;
2479	}
2480
2481	base = strrchr(dst->path, '/');
2482	if (base == NULL) return NULL;
2483
2484	*base = '\0';
2485	base++;
2486
2487	out = asl_list_log_files(dst->path, base, true, flags);
2488
2489	if (base != NULL) *--base = '/';
2490
2491	return out;
2492}
2493
2494/*
2495 * List the destination files for an output asl_out_dst_data_t
2496 */
2497asl_out_file_list_t *
2498asl_list_dst_files(asl_out_dst_data_t *dst)
2499{
2500	char *base, *dst_dir;
2501	asl_out_file_list_t *out;
2502
2503	if (dst == NULL) return NULL;
2504	if (dst->path == NULL) return NULL;
2505
2506	base = strrchr(dst->path, '/');
2507	if (base == NULL) return NULL;
2508
2509	*base = '\0';
2510	base++;
2511
2512	dst_dir = dst->rotate_dir;
2513	if (dst_dir == NULL) dst_dir = dst->path;
2514
2515	out = asl_list_log_files(dst_dir, base, false, dst->flags);
2516
2517	if (base != NULL) *--base = '/';
2518
2519	return out;
2520}
2521
2522static int
2523asl_secure_open_dir(const char *path)
2524{
2525	int fd, i;
2526	char **path_parts;
2527
2528	if (path == NULL) return -1;
2529	if (path[0] != '/') return -1;
2530
2531	path_parts = explode(path + 1, "/");
2532	if (path_parts == NULL) return 0;
2533
2534	fd = open("/", O_RDONLY | O_NOFOLLOW, 0);
2535	if (fd < 0)
2536	{
2537		free_string_list(path_parts);
2538		return -1;
2539	}
2540
2541	for (i = 0; path_parts[i] != NULL; i++)
2542	{
2543		int fd_next, status;
2544		struct stat sb;
2545
2546		fd_next = openat(fd, path_parts[i], O_RDONLY | O_NOFOLLOW, 0);
2547		close(fd);
2548		fd = fd_next;
2549		if (fd < 0)
2550		{
2551			free_string_list(path_parts);
2552			return -1;
2553		}
2554
2555		memset(&sb, 0, sizeof(sb));
2556
2557		status = fstat(fd, &sb);
2558		if (status < 0)
2559		{
2560			free_string_list(path_parts);
2561			return -1;
2562		}
2563
2564		if (!S_ISDIR(sb.st_mode))
2565		{
2566			free_string_list(path_parts);
2567			return -1;
2568		}
2569	}
2570
2571	free_string_list(path_parts);
2572	return fd;
2573}
2574
2575int
2576asl_secure_chown_chmod_dir(const char *path, uid_t uid, gid_t gid, mode_t mode)
2577{
2578	int fd, status;
2579
2580	fd = asl_secure_open_dir(path);
2581	if (fd < 0) return fd;
2582
2583	status = fchown(fd, uid, gid);
2584	if (status < 0)
2585	{
2586		close(fd);
2587		return -1;
2588	}
2589
2590	if (mode >= 0) status = fchmod(fd, mode);
2591	close(fd);
2592
2593	if (status < 0) return -1;
2594	return 0;
2595}
2596