1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*
27 * Command line option processing for auditreduce.
28 * The entry point is process_options(), which is called by main().
29 * Process_options() is the only function visible outside this module.
30 */
31
32#include <locale.h>
33#include <sys/zone.h>	/* for max zonename length */
34#include "auditr.h"
35
36/*
37 * Object entry.
38 * Maps object strings specified on the command line to a flag
39 * used when searching by object type.
40 */
41
42struct obj_ent {
43	char	*obj_str; /* string specified on the command line */
44	int	obj_flag; /* flag used when searching */
45};
46
47typedef struct obj_ent obj_ent_t;
48
49/*
50 * Supports searches by object type.
51 */
52static obj_ent_t obj_tbl[] = {
53			{ "file", OBJ_PATH },
54			{ "filegroup", OBJ_FGROUP },
55			{ "fileowner", OBJ_FOWNER },
56			{ "fmri", OBJ_FMRI },
57			{ "lp", OBJ_LP   },
58			{ "msgqid", OBJ_MSG  },
59			{ "msgqgroup", OBJ_MSGGROUP },
60			{ "msgqowner", OBJ_MSGOWNER },
61			{ "path", OBJ_PATH },
62			{ "pid", OBJ_PROC },
63			{ "procgroup", OBJ_PGROUP },
64			{ "procowner", OBJ_POWNER },
65			{ "semid", OBJ_SEM  },
66			{ "semgroup", OBJ_SEMGROUP  },
67			{ "semowner", OBJ_SEMOWNER  },
68			{ "shmid", OBJ_SHM  },
69			{ "shmgroup", OBJ_SHMGROUP  },
70			{ "shmowner", OBJ_SHMOWNER  },
71			{ "sock", OBJ_SOCK },
72			{ "user", OBJ_USER } };
73
74extern int	derive_date(char *, struct tm *);
75extern int	parse_time(char *, int);
76extern char	*re_comp2(char *);
77extern time_t	tm_to_secs(struct tm *);
78
79static int	a_isnum(char *, int);
80static int	check_file(audit_fcb_t *, int);
81static int	gather_dir(char *);
82static audit_pcb_t *get_next_pcb(char *);
83static obj_ent_t *obj_lkup(char *);
84static int	proc_class(char *);
85static int	proc_date(char *, int);
86static int	proc_file(char *, int);
87static int	process_fileopt(int, char *argv[], int);
88static int	proc_group(char *, gid_t *);
89static int	proc_id(char *, int);
90static int	proc_object(char *);
91static void	proc_pcb(audit_pcb_t *, char *, int);
92static int	proc_label(char *);
93static int	proc_subject(char *);
94static int	proc_sid(char *);
95static int	proc_type(char *);
96static int	proc_user(char *, uid_t *);
97static int	proc_zonename(char *);
98static int	proc_fmri(char *);
99
100/*
101 * .func	process_options - process command line options.
102 * .desc	Process the user's command line options. These are of two types:
103 *	single letter flags that are denoted by '-', and filenames. Some
104 *	of the flags have arguments. Getopt() is used to get the flags.
105 *	When this is done it calls process_fileopt() to handle any filenames
106 *	that were there.
107 * .call	ret = process_options(argc, argv).
108 * .arg	argc	- the original value.
109 * .arg	argv	- the original value.
110 * .ret	0	- no errors detected.
111 * .ret	-1	- command line error detected (message already printed).
112 */
113int
114process_options(int argc, char **argv)
115{
116	int	opt;
117	int	error = FALSE;
118	int	error_combo = FALSE;
119	extern int	optind;		/* in getopt() */
120	extern char	*optarg;	/* in getopt() - holds arg to flag */
121
122	static char	*options = "ACD:M:NQR:S:VO:"
123	    "a:b:c:d:e:g:j:l:m:o:r:s:t:u:z:";
124
125	error_str = gettext("general error");
126
127	zonename = NULL;
128	/*
129	 * Big switch to process the flags.
130	 * Start_over: is for handling the '-' for standard input. Getopt()
131	 * doesn't recognize it.
132	 */
133start_over:
134	while ((opt = getopt(argc, argv, options)) != EOF) {
135		switch (opt) {
136		case 'A':		/* all records from the files */
137			f_all = TRUE;
138			break;
139		case 'C':		/* process only completed files */
140			f_complete = TRUE;
141			break;
142		case 'D':		/* delete the files when done */
143			/* force 'A' 'C' 'O' to be active */
144			f_all = f_complete = TRUE;
145			f_outfile = optarg;
146			f_delete = TRUE;
147			break;
148		case 'M':		/* only files from a certain machine */
149			f_machine = optarg;
150			break;
151		case 'N':		/* new object selection mode */
152			new_mode = TRUE;
153			break;
154		case 'Q':		/* no file error reporting */
155			f_quiet = TRUE;
156			break;
157		case 'R':		/* from specified root */
158			f_root = optarg;
159			break;
160		case 'S':		/* from specified server */
161			f_server = optarg;
162			break;
163		case 'V':		/* list all files as they are opened */
164			f_verbose = TRUE;
165			break;
166		case 'O':		/* write to outfile */
167			f_outfile = optarg;
168			break;
169		case 'a':		/* after 'date' */
170		case 'b':		/* before 'date' */
171		case 'd':		/* from 'day' */
172			if (proc_date(optarg, opt))
173				error = TRUE;
174			break;
175		case 'j':		/* subject */
176			if (proc_subject(optarg))
177				error = TRUE;
178			break;
179		case 'm':		/* message 'type' */
180			if (proc_type(optarg))
181				error = TRUE;
182			break;
183		case 'o':		/* object type */
184			if (proc_object(optarg))
185				error = TRUE;
186			break;
187		case 'c':		/* message class */
188			if (proc_class(optarg))
189				error = TRUE;
190			break;
191		case 'u':		/* form audit user */
192		case 'e':		/* form effective user */
193		case 'r':		/* form real user */
194		case 'f':		/* form effective group */
195		case 'g':		/* form real group */
196			if (proc_id(optarg, opt))
197				error = TRUE;
198			break;
199		case 'l':		/* TX label range */
200			if (!is_system_labeled()) {
201				(void) fprintf(stderr,
202				    gettext("%s option 'l' requires "
203				    "Trusted Extensions.\n"), ar);
204				return (-1);
205			}
206			if (proc_label(optarg))
207				error = TRUE;
208			break;
209		case 's':		/* session ID */
210			if (proc_sid(optarg))
211				error = TRUE;
212			break;
213		case 'z':		/* zone name */
214			if (proc_zonename(optarg))
215				error = TRUE;
216			break;
217		case 't':		/* termial ID reserved for later */
218		default:
219			return (-1);
220		}
221		if (error) {
222			(void) fprintf(stderr,
223			    gettext("%s command line error - %s.\n"),
224			    ar, error_str);
225			return (-1);
226		}
227	}
228	/* catch '-' option for stdin processing - getopt() won't see it */
229	if (optind < argc) {
230		if (argv[optind][0] == '-' && argv[optind][1] == '\0') {
231			optind++;
232			f_stdin = TRUE;
233			goto start_over;
234		}
235	}
236	/*
237	 * Give a default value for 'b' option if not specified.
238	 */
239	if (m_before == 0)
240		m_before = MAXLONG;	/* forever */
241	/*
242	 * Validate combinations of options.
243	 * The following are done:
244	 *	1. Can't have 'M' or 'S' or 'R' with filenames.
245	 *	2. Can't have an after ('a') time after a before ('b') time.
246	 *	3. Delete ('D') must have 'C' and 'A' and 'O' with it.
247	 *	4. Input from stdin ('-') can't have filenames too.
248	 */
249	if ((f_machine || f_server || f_root) && (argc != optind)) {
250		error_str = gettext(
251		    "no filenames allowed with 'M' or 'S' or 'R' options");
252		error_combo = TRUE;
253	}
254	if (m_after >= m_before) {
255		error_str =
256		    gettext("'a' parameter must be before 'b' parameter");
257		error_combo = TRUE;
258	}
259	if (f_delete &&
260	    (!f_complete || !f_all || !f_outfile)) {
261		error_str = gettext(
262		    "'C', 'A', and 'O' must be specified with 'D'");
263		error_combo = TRUE;
264	}
265	if (f_stdin && (argc != optind)) {
266		error_str = gettext("no filenames allowed with '-' option");
267		error_combo = TRUE;
268	}
269	/*
270	 * If error with option combos then print message and exit.
271	 * If there was an error with just an option then exit.
272	 */
273	if (error_combo) {
274		(void) fprintf(stderr,
275		    gettext("%s command line error - %s.\n"), ar, error_str);
276		return (-1);
277	}
278	if (f_root == NULL)
279		f_root = "/etc/security/audit";
280	/*
281	 * Now handle any filenames included in the command line.
282	 */
283	return (process_fileopt(argc, argv, optind));
284}
285
286int
287proc_subject(char *optarg)
288{
289	if (flags & M_SUBJECT) {
290		error_str = gettext("'j' option specified multiple times");
291		return (-1);
292	}
293	flags |= M_SUBJECT;
294	subj_id = atol(optarg);
295	return (0);
296}
297
298int
299proc_sid(char *optarg)
300{
301	if (flags & M_SID) {
302		error_str = gettext("'s' option specified multiple times");
303		return (-1);
304	}
305	flags |= M_SID;
306	m_sid = (au_asid_t)atol(optarg);
307	return (0);
308}
309
310int
311proc_object(char *optarg)
312{
313	char	*obj_str;
314	char	*obj_val;
315	char	*obj_arg;
316	int	err;
317
318	obj_ent_t *oep;
319	struct hostent *he;
320
321	if (flags & M_OBJECT) {
322		error_str = gettext("'o' option specified multiple times");
323		return (-1);
324	}
325	flags |= M_OBJECT;
326	if ((obj_arg = strdup(optarg)) == (char *)0)
327		return (-1);
328	if ((obj_str = strtok(optarg, "=")) == (char *)0 ||
329	    (oep = obj_lkup(obj_str)) == (obj_ent_t *)0 ||
330	    (obj_val = strtok((char *)0, "=")) == (char *)0) {
331		(void) sprintf(errbuf, gettext("invalid object arg (%s)"),
332		    obj_arg);
333		error_str = errbuf;
334		return (-1);
335	}
336
337	obj_flag = oep->obj_flag;
338
339	switch (obj_flag) {
340	case OBJ_PATH:
341		if ((error_str = re_comp2(obj_val)) != (char *)NULL) {
342			return (-1);
343		}
344		return (0);
345	case OBJ_SOCK:
346		if (!a_isnum(obj_val, TRUE)) {
347			obj_id = atol(obj_val);
348			socket_flag = SOCKFLG_PORT;
349			return (0);
350		}
351		if (*obj_val == '0') {
352			(void) sscanf(obj_val, "%x", (uint_t *)&obj_id);
353			socket_flag = SOCKFLG_PORT;
354			return (0);
355		}
356
357		he = getipnodebyname((const void *)obj_val, AF_INET6, 0, &err);
358		if (he == 0) {
359			he = getipnodebyname((const void *)obj_val, AF_INET,
360			    0, &err);
361			if (he == 0) {
362				(void) sprintf(errbuf,
363				    gettext("invalid machine name (%s)"),
364				    obj_val);
365				error_str = errbuf;
366				return (-1);
367			}
368		}
369
370		if (he->h_addrtype == AF_INET6) {
371			/* LINTED */
372			if (IN6_IS_ADDR_V4MAPPED(
373			    (in6_addr_t *)he->h_addr_list[0])) {
374				/* address is IPv4 (32 bits) */
375				(void) memcpy(&obj_id,
376				    he->h_addr_list[0] + 12, 4);
377				ip_type = AU_IPv4;
378			} else {
379				(void) memcpy(ip_ipv6, he->h_addr_list[0], 16);
380				ip_type = AU_IPv6;
381			}
382		} else {
383			/* address is IPv4 (32 bits) */
384			(void) memcpy(&obj_id, he->h_addr_list[0], 4);
385			ip_type = AU_IPv4;
386		}
387
388		freehostent(he);
389		socket_flag = SOCKFLG_MACHINE;
390		return (0);
391	case OBJ_MSG:
392	case OBJ_SEM:
393	case OBJ_SHM:
394	case OBJ_PROC:
395		obj_id = atol(obj_val);
396		return (0);
397	case OBJ_FGROUP:
398	case OBJ_MSGGROUP:
399	case OBJ_SEMGROUP:
400	case OBJ_SHMGROUP:
401	case OBJ_PGROUP:
402		return (proc_group(obj_val, &obj_group));
403	case OBJ_FOWNER:
404	case OBJ_MSGOWNER:
405	case OBJ_SEMOWNER:
406	case OBJ_SHMOWNER:
407	case OBJ_POWNER:
408		return (proc_user(obj_val, &obj_owner));
409	case OBJ_FMRI:
410		return (proc_fmri(obj_val));
411	case OBJ_USER:
412		return (proc_user(obj_val, &obj_user));
413	case OBJ_LP: /* lp objects have not yet been defined */
414	default: /* impossible */
415		(void) sprintf(errbuf, gettext("invalid object type (%s)"),
416		    obj_str);
417		error_str = errbuf;
418		return (-1);
419	} /* switch */
420	/*NOTREACHED*/
421}
422
423
424obj_ent_t *
425obj_lkup(char *obj_str)
426{
427	int	i;
428
429	for (i = 0; i < sizeof (obj_tbl) / sizeof (obj_ent_t); i++)
430		if (strcmp(obj_str, obj_tbl[i].obj_str) == 0)
431			return (&obj_tbl[i]);
432
433	/* not in table */
434	return (NULL);
435}
436
437
438/*
439 * .func	proc_type - process record type.
440 * .desc	Process a record type. It is either as a number or a mnemonic.
441 * .call	ret = proc_type(optstr).
442 * .arg	optstr	- ptr to name or number.
443 * .ret	0	- no errors detected.
444 * .ret	-1	- error detected (error_str contains description).
445 */
446int
447proc_type(char *optstr)
448{
449	struct au_event_ent *aep;
450
451	/*
452	 * Either a number or a name.
453	 */
454
455	if (flags & M_TYPE) {
456		error_str = gettext("'m' option specified multiple times");
457		return (-1);
458	}
459	flags |= M_TYPE;
460	m_type = 0;
461	if (a_isnum(optstr, TRUE)) {
462		if ((aep = getauevnam(optstr)) != NULL)
463			m_type = aep->ae_number;
464	} else {
465		if ((aep = getauevnum((au_event_t)atoi(optstr))) !=
466		    (struct au_event_ent *)NULL)
467			m_type = aep->ae_number;
468	}
469	if ((m_type == 0)) {
470		(void) sprintf(errbuf, gettext("invalid event (%s)"), optstr);
471		error_str = errbuf;
472		return (-1);
473	}
474	return (0);
475}
476
477
478/*
479 * .func	a_isnum - is it a number?
480 * .desc	Determine if a string is a number or a name.
481 *	A number may have a leading '+' or '-', but then must be
482 *	all digits.
483 * .call	ret = a_isnum(str).
484 * .arg	str - ptr to the string.
485 * .arg	leading	- TRUE if leading '+-' allowed.
486 * .ret	0	- is a number.
487 * .ret	1	- is not a number.
488 */
489int
490a_isnum(char *str, int leading)
491{
492	char	*strs;
493
494	if ((leading == TRUE) && (*str == '-' || *str == '+'))
495		strs = str + 1;
496	else
497		strs = str;
498
499	if (strlen(strs) == strspn(strs, "0123456789"))
500		return (0);
501	else
502		return (1);
503}
504
505
506/*
507 * .func	proc_id	- process user/group id's/
508 * .desc	Process either a user number/name or group number/name.
509 *	For names check to see if the name is active in the system
510 *	to derive the number. If it is not active then fail. For a number
511 *	also check to see if it is active, but only print a warning if it
512 *	is not. An administrator may be looking at activity of a 'phantom'
513 *	user.
514 * .call	ret = proc_id(optstr, opt).
515 * .arg	optstr	- ptr to name or number.
516 * .arg	opt	- 'u' - audit user, 'e' - effective user, 'r' - real user,
517 *		  'g' - group, 'f' - effective group.
518 * .ret	0	- no errors detected.
519 * .ret	-1	- error detected (error_str contains description).
520 */
521int
522proc_id(char *optstr, int opt)
523{
524	switch (opt) {
525	case 'e': 		/* effective user id */
526		if (flags & M_USERE) {
527			error_str = gettext(
528			    "'e' option specified multiple times");
529			return (-1);
530		}
531		flags |= M_USERE;
532		return (proc_user(optstr, &m_usere));
533	case 'f': 		/* effective group id */
534		if (flags & M_GROUPE) {
535			error_str = gettext(
536			    "'f' option specified multiple times");
537			return (-1);
538		}
539		flags |= M_GROUPE;
540		return (proc_group(optstr, &m_groupe));
541	case 'r': 		/* real user id */
542		if (flags & M_USERR) {
543			error_str = gettext(
544			    "'r' option specified multiple times");
545			return (-1);
546		}
547		flags |= M_USERR;
548		return (proc_user(optstr, &m_userr));
549	case 'u': 		/* audit user id */
550		if (flags & M_USERA) {
551			error_str = gettext(
552			    "'u' option specified multiple times");
553			return (-1);
554		}
555		flags |= M_USERA;
556		return (proc_user(optstr, &m_usera));
557	case 'g': 		/* real group id */
558		if (flags & M_GROUPR) {
559			error_str = gettext(
560			    "'g' option specified multiple times");
561			return (-1);
562		}
563		flags |= M_GROUPR;
564		return (proc_group(optstr, &m_groupr));
565	default: 		/* impossible */
566		(void) sprintf(errbuf, gettext("'%c' unknown option"), opt);
567		error_str = errbuf;
568		return (-1);
569	}
570	/*NOTREACHED*/
571}
572
573
574int
575proc_group(char *optstr, gid_t *gid)
576{
577	struct group *grp;
578
579	if ((grp = getgrnam(optstr)) == NULL) {
580		if (!a_isnum(optstr, TRUE)) {
581			*gid = (gid_t)atoi(optstr);
582			return (0);
583		}
584		(void) sprintf(errbuf, gettext("group name invalid (%s)"),
585		    optstr);
586		error_str = errbuf;
587		return (-1);
588	}
589	*gid = grp->gr_gid;
590	return (0);
591}
592
593
594int
595proc_user(char *optstr, uid_t *uid)
596{
597	struct passwd *usr;
598
599	if ((usr = getpwnam(optstr)) == NULL) {
600		if (!a_isnum(optstr, TRUE)) {
601			*uid = (uid_t)atoi(optstr);
602			return (0);
603		}
604		(void) sprintf(errbuf, gettext("user name invalid (%s)"),
605		    optstr);
606		error_str = errbuf;
607		return (-1);
608	}
609	*uid = usr->pw_uid;
610	return (0);
611}
612
613
614/*
615 * .func proc_date - process date argument.
616 * .desc Handle a date/time argument. See if the user has erred in combining
617 *	the types of date arguments. Then parse the string and check for
618 *	validity of each part.
619 * .call	ret = proc_date(optstr, opt).
620 * .arg	optstr	- ptr to date/time string.
621 * .arg	opt	- 'd' for day, 'a' for after, or 'b' for before.
622 * .ret	0	- no errors detected.
623 * .ret	-1	- errors detected (error_str knows what it is).
624 */
625int
626proc_date(char *optstr, int opt)
627{
628	static int	m_day = FALSE;
629
630	if (opt == 'd') {
631		if (m_day == TRUE) {
632			error_str = gettext(
633			    "'d' option may not be used with 'a' or 'b'");
634			return (-1);
635		}
636		m_day = TRUE;
637	}
638	if ((opt == 'd') && (m_before || m_after)) {
639		error_str = gettext(
640		    "'d' option may not be used with 'a' or 'b'");
641		return (-1);
642	}
643	if ((opt == 'a' || opt == 'b') && m_day) {
644		error_str = gettext(
645		    "'a' or 'b' option may not be used with 'd'");
646		return (-1);
647	}
648	if ((opt == 'a') && (m_after != 0)) {
649		error_str = gettext("'a' option specified multiple times");
650		return (-1);
651	}
652	if ((opt == 'b') && (m_before != 0)) {
653		error_str = gettext("'b' option specified multiple times");
654		return (-1);
655	}
656	if (parse_time(optstr, opt))
657		return (-1);
658	return (0);
659}
660
661
662/*
663 * .func	proc_class - process message class argument.
664 * .desc	Process class type and see if it is for real.
665 * .call	ret = proc_class(optstr).
666 * .arg	optstr	- ptr to class.
667 * .ret	0	- class has class.
668 * .ret	-1	- class in no good.
669 */
670int
671proc_class(char *optstr)
672{
673	if (flags & M_CLASS) {
674		error_str = gettext("'c' option specified multiple times");
675		return (-1);
676	}
677	flags |= M_CLASS;
678
679	if (getauditflagsbin(optstr, &mask) != 0) {
680		(void) sprintf(errbuf, gettext("unknown class (%s)"), optstr);
681		error_str = errbuf;
682		return (-1);
683	}
684
685	if (mask.am_success != mask.am_failure) {
686		flags |= M_SORF;
687	}
688
689	return (0);
690}
691
692
693/*
694 * .func process_fileopt - process command line file options.
695 * .desc Process the command line file options and gather the specified files
696 *	together in file groups based upon file name suffix. The user can
697 *	specify files explicitly on the command line or via a directory.
698 *	This is called after the command line flags are processed (as
699 *	denoted by '-').
700 * .call	ret = process_fileopt(argc, argv, optindex).
701 * .arg	argc	- current value of argc.
702 * .arg	argv	- current value of argv.
703 * .arg	optindex- current index into argv (as setup by getopt()).
704 * .ret	0	- no errors detected.
705 * .ret	-1	- error detected (message already printed).
706 */
707int
708process_fileopt(int argc, char **argv, int optindex)
709{
710	int	f_mode = FM_ALLDIR;
711	char	f_dr[MAXNAMLEN+1];
712	char	*f_dir = f_dr;
713	char	*fname;
714	static char	*std = "standard input";
715	audit_fcb_t *fcb;
716	DIR * dirp;
717	struct dirent *dp;
718	audit_pcb_t *pcb;
719
720	/*
721	 * Take input from stdin, not any files.
722	 * Use a single fcb to do this.
723	 */
724	if (f_stdin) {
725		fcb = (audit_fcb_t *)a_calloc(1, sizeof (*fcb) + strlen(std));
726		(void) strcpy(fcb->fcb_file, std);
727		fcb->fcb_suffix = fcb->fcb_name = fcb->fcb_file;
728		fcb->fcb_next = NULL;
729		fcb->fcb_start = 0;
730		fcb->fcb_end = MAXLONG;		/* forever */
731		if ((pcb = get_next_pcb((char *)NULL)) == (audit_pcb_t *)NULL)
732			return (-1);
733		pcb->pcb_suffix = fcb->fcb_file;
734		pcb->pcb_dfirst = pcb->pcb_first = fcb;	/* one-item list */
735		pcb->pcb_dlast = pcb->pcb_last = fcb;
736		pcb->pcb_cur = fcb;
737	}
738	/*
739	 * No files specified on the command line.
740	 * Process a directory of files or subdirectories.
741	 */
742	else if (argc == optindex) {
743		/*
744		 * A specific server directory was requested.
745		 */
746		if (f_server) {
747			if (strchr(f_server, '/')) {	/* given full path */
748				f_dir = f_server;
749				f_mode = FM_ALLFILE;	/* all files here */
750			} else {		/* directory off audit root */
751				f_dir[0] = '\0';
752				(void) strcat(f_dir, f_root);
753				(void) strcat(f_dir, "/");
754				(void) strcat(f_dir, f_server);
755				f_mode = FM_ALLFILE;
756			}
757		}
758		/*
759		 * Gather all of the files in the directory 'f_dir'.
760		 */
761		if (f_mode == FM_ALLFILE) {
762			if (gather_dir(f_dir)) { /* get those files together */
763				return (-1);
764			}
765		} else {
766			/*
767			 * Gather all of the files in all of the
768			 * directories in 'f_root'.
769			 */
770			if ((dirp = opendir(f_root)) == NULL) {
771				(void) sprintf(errbuf, gettext(
772				    "%s can't open directory %s"), ar, f_root);
773				perror(errbuf);
774				return (-1);
775			}
776			/* read the directory and process all of the subs */
777			for (dp = readdir(dirp);
778			    dp != NULL; dp = readdir(dirp)) {
779				if (dp->d_name[0] == '.')
780					continue;
781				f_dir[0] = '\0';
782				(void) strcat(f_dir, f_root);
783				(void) strcat(f_dir, "/");
784				(void) strcat(f_dir, dp->d_name);
785				if (gather_dir(f_dir))	/* process a sub */
786					return (-1);
787			}
788			(void) closedir(dirp);
789		}
790	} else {
791		/*
792		 * User specified filenames on the comm and line.
793		 */
794		f_cmdline = TRUE;
795		for (; optindex < argc; optindex++) {
796			fname = argv[optindex];		/* get a filename */
797			if (proc_file(fname, FALSE))
798				return (-1);
799		}
800	}
801	return (0);
802}
803
804
805/*
806 * .func	gather_dir - gather a directory's files together.
807 * .desc	Process all of the files in a specific directory. The files may
808 *	be checked for adherence to the file name form at.
809 *	If the directory can't be opened that is ok - just print
810 *	a message and continue.
811 * .call	ret = gather_dir(dir).
812 * .arg	dir	- ptr to full pathname of directory.
813 * .ret	0	- no errors detected.
814 * .ret	-1	- error detected (message already printed).
815 */
816int
817gather_dir(char *dir)
818{
819	char	dname[MAXNAMLEN+1];
820	char	fname[MAXNAMLEN+1];
821	DIR * dirp;
822	struct dirent *dp;
823
824	(void) snprintf(dname, sizeof (dname), "%s/files", dir);
825
826	if ((dirp = opendir(dname)) == NULL) {
827		if (errno != ENOTDIR) {
828			(void) sprintf(errbuf,
829			    gettext("%s can't open directory - %s"), ar, dname);
830			perror(errbuf);
831		}
832		return (0);
833	}
834	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
835		if (dp->d_name[0] == '.')	/* can't see hidden files */
836			continue;
837		fname[0] = '\0';
838		(void) strcat(fname, dname);	/* create pathname of file */
839		(void) strcat(fname, "/");
840		(void) strcat(fname, dp->d_name);
841		if (proc_file(fname, TRUE))
842			return (-1);
843	}
844	(void) closedir(dirp);
845	return (0);
846}
847
848
849/*
850 * .func	proc_file - process a single candidate file.
851 * .desc	Check out a file to see if it should be used in the merge.
852 *	This includes checking the name (mode is TRUE) against the
853 *	file format, checking access rights to the file, and thence
854 *	getting and fcb and installing the fcb into the correct pcb.
855 *	If the file fails then the fcb is not installed into a pcb
856 *	and the file dissapears from view.
857 * .call	proc_file(fname, mode).
858 * .arg	fname	- ptr to full pathna me of file.
859 * .arg	mode	- TRUE if checking adherence to file name format.
860 * .ret	0	- no fatal errors detected.
861 * .ret	-1	- fatal error detected - quit altogether
862 *		  (message already printed).
863 */
864int
865proc_file(char *fname, int mode)
866{
867	int reject = FALSE;
868	size_t len;
869	struct stat stat_buf;
870	audit_fcb_t *fcb, *fcbp, *fcbprev;
871	audit_pcb_t *pcb;
872
873	/*
874	 * See if it is a weird file like a directory or
875	 * character special (around here?).
876	 */
877	if (stat(fname, &stat_buf)) {
878		return (0);
879	}
880	if (!S_ISREG(stat_buf.st_mode))
881		return (0);
882	/*
883	 * Allocate a new fcb to hold fcb and full filename.
884	 */
885	len = sizeof (audit_fcb_t) + strlen(fname);
886	fcb = (audit_fcb_t *)a_calloc(1, len);
887	(void) strcpy(fcb->fcb_file, fname);
888	if (check_file(fcb, mode)) { /* check file name */
889		if (!f_quiet) {
890			(void) fprintf(stderr, "%s %s:\n  %s.\n", ar,
891			    error_str, fname);
892		}
893		reject = TRUE;
894	} else {
895		/*
896		 * Check against file criteria.
897		 * Check finish-time here, and start-time later on
898		 * while processing.
899		 * This is because the start time on a file can be after
900		 * the first record(s).
901		 */
902		if (f_complete && (fcb->fcb_flags & FF_NOTTERM) && !f_cmdline)
903			reject = TRUE;
904		if (!f_all && (fcb->fcb_end < m_after))
905			reject = TRUE;
906		if (f_machine) {
907			if (strlen(fcb->fcb_suffix) != strlen(f_machine) ||
908			    (strcmp(fcb->fcb_suffix, f_machine) != 0)) {
909				reject = TRUE;
910			}
911		}
912	}
913	if (reject == FALSE) {
914		filenum++;	/* count of total files to be processed */
915		fcb->fcb_next = NULL;
916		if ((pcb = get_next_pcb(fcb->fcb_suffix)) == NULL) {
917			return (-1);
918		}
919		/* Place FCB into the PCB in order - oldest first.  */
920		fcbp = pcb->pcb_first;
921		fcbprev = NULL;
922		while (fcbp != NULL) {
923			if (fcb->fcb_start < fcbp->fcb_start) {
924				if (fcbprev)
925					fcbprev->fcb_next = fcb;
926				else
927					pcb->pcb_dfirst = pcb->pcb_first = fcb;
928				fcb->fcb_next = fcbp;
929				break;
930			}
931			fcbprev = fcbp;
932			fcbp = fcbp->fcb_next;
933		}
934		/* younger than all || empty list */
935		if (!fcb->fcb_next) {
936			if (pcb->pcb_first == NULL)
937				pcb->pcb_dfirst = pcb->pcb_first = fcb;
938			pcb->pcb_dlast = pcb->pcb_last = fcb;
939			if (fcbprev)
940				fcbprev->fcb_next = fcb;
941		}
942	} else {
943		free((char *)fcb);	/* rejected */
944	}
945	return (0);
946}
947
948
949/*
950 * .func	check_file - check filename and setup fcb.
951 * .desc	Check adherence to the file format (do_check is TRUE) and setup
952 *	the fcb with useful information.
953 *	filename format: yyyymmddhhmmss.yyyymmddhhmmss.suffix
954 *			 yyyymmddhhmmss.not_terminated.suffix
955 *	If do_check is FALSE then still see if the filename does confirm
956 *	to the format. If it does then extract useful information from
957 *	it (start time and end time).  But if it doesn't then don't print
958 *	any error messages.
959 * .call	ret = check_file(fcb, do_check).
960 * .arg	fcb	- ptr to fcb that holds the file.
961 * .arg	do_check - if TRUE do check adherence to file format.
962 * .ret	0	- no errors detected.
963 * .ret	-1	- file failed somehow (error_str tells why).
964 */
965int
966check_file(audit_fcb_t *fcb, int do_check)
967{
968	int	ret;
969	char	*namep, *slp;
970	char	errb[256];		/* build error message */
971	struct tm tme;
972
973	errb[0] = '\0';
974	/* get just the filename */
975	for (slp = namep = fcb->fcb_file; *namep; namep++) {
976		if (*namep == '/')
977			slp = namep + 1; /* slp -> the filename itself */
978	}
979	if (do_check == FALSE) {
980		fcb->fcb_end = MAXLONG;		/* forever */
981		fcb->fcb_suffix = NULL;
982		fcb->fcb_name = slp;
983		ret = 0;
984	} else {
985		ret = -1;
986	}
987	if ((int)strlen(slp) < 31) {
988		(void) sprintf(errbuf, gettext("filename too short (%d)"),
989		    strlen(slp));
990		error_str = errbuf;
991		return (ret);
992	}
993	/*
994	 * Get working copy of filename.
995	 */
996	namep = (char *)a_calloc(1, strlen(slp) + 1);
997	(void) strcpy(namep, slp);
998	if (namep[14] != '.' || namep[29] != '.') {
999		(void) sprintf(errbuf,
1000		    gettext("invalid filename format (%c or %c)"), namep[14],
1001		    namep[29]);
1002		error_str = errbuf;
1003		free(namep);
1004		return (ret);
1005	}
1006	namep[14] = '\0';			/* mark off start time */
1007	namep[29] = '\0';			/* mark off finish time */
1008	if (derive_date(namep, &tme)) {
1009		(void) strcat(errb, gettext("starting time-stamp invalid - "));
1010		(void) strcat(errb, error_str);
1011		(void) strcpy(errbuf, errb);
1012		error_str = errbuf;
1013		free(namep);
1014		return (ret);
1015	}
1016	/*
1017	 * Keep start time from filename. Use it to order files in
1018	 * the file list. Later we will update this when we read
1019	 * the first record from the file.
1020	 */
1021	fcb->fcb_start = tm_to_secs(&tme);
1022
1023	if (strcmp(&namep[15], "not_terminated") == 0) {
1024		fcb->fcb_end = MAXLONG;		/* forever */
1025		/*
1026		 * Only treat a 'not_terminated' file as such if
1027		 * it is not on the command line.
1028		 */
1029		if (do_check == TRUE)
1030			fcb->fcb_flags |= FF_NOTTERM;
1031	} else if (derive_date(&namep[15], &tme)) {
1032		(void) strcat(errb, gettext("ending time-stamp invalid - "));
1033		(void) strcat(errb, error_str);
1034		(void) strcpy(errbuf, errb);
1035		error_str = errbuf;
1036		free(namep);
1037		return (ret);
1038	} else {
1039		fcb->fcb_end = tm_to_secs(&tme);
1040	}
1041	fcb->fcb_name = slp;
1042	fcb->fcb_suffix = &slp[30];
1043	free(namep);
1044	return (0);
1045}
1046
1047
1048/*
1049 * .func get_next_pcb - get a pcb to use.
1050 * .desc	The pcb's in the array audit_pcbs are used to hold single file
1051 *	groups in the form of a linked list. Each pcb holds files that
1052 *	are tied together by a common suffix in the file name. Here we
1053 *	get either 1. the existing pcb holding a specified sufix or
1054 *	2. a new pcb if we can't find an existing one.
1055 * .call	pcb = get_next_pcb(suffix).
1056 * .arg	suffix	- ptr to suffix we are seeking.
1057 * .ret	pcb	- ptr to pcb that hold s the sought suffix.
1058 * .ret	NULL- serious failure in memory allocation. Quit processing.
1059 */
1060audit_pcb_t *
1061get_next_pcb(char *suffix)
1062{
1063	int	i = 0;
1064	int	zerosize;
1065	unsigned int	size;
1066	audit_pcb_t *pcb;
1067
1068	/* Search through (maybe) entire array. */
1069	while (i < pcbsize) {
1070		pcb = &audit_pcbs[i++];
1071		if (pcb->pcb_first == NULL) {
1072			proc_pcb(pcb, suffix, i);
1073			return (pcb);	/* came to an unused one */
1074		}
1075		if (suffix) {
1076			if (strcmp(pcb->pcb_suffix, suffix) == 0)
1077				return (pcb);	/* matched one with suffix */
1078		}
1079	}
1080	/*
1081	 * Uh-oh, the entire array is used and we haven't gotten one yet.
1082	 * Allocate a bigger array.
1083	 */
1084	pcbsize += PCB_INC;
1085	size = pcbsize * sizeof (audit_pcb_t);
1086	zerosize = size - ((pcbsize - PCB_INC) * sizeof (audit_pcb_t));
1087	if ((audit_pcbs = (audit_pcb_t *)realloc((char *)audit_pcbs, size)) ==
1088	    NULL) {
1089		(void) sprintf(errbuf,
1090		    gettext("%s memory reallocation failed (%d bytes)"), ar,
1091		    size);
1092		perror(errbuf);
1093		audit_stats();		/* give user statistics on usage */
1094		return (NULL);		/* really bad thing to have happen */
1095	}
1096	/*
1097	 * Don't know if realloc clears the new memory like calloc would.
1098	 */
1099	(void) memset((void *) & audit_pcbs[pcbsize-PCB_INC], 0,
1100	    (size_t)zerosize);
1101	pcb = &audit_pcbs[pcbsize-PCB_INC];	/* allocate the first new one */
1102	proc_pcb(pcb, suffix, pcbsize - PCB_INC);
1103	return (pcb);
1104}
1105
1106
1107/*
1108 * .func proc_pcb - process pcb.
1109 * .desc	Common pcb processing for above routine.
1110 * .call	proc_pcb(pcb, suffix, i).
1111 * .arg	pcb	- ptr to pcb.
1112 * .arg	suffix	- prt to suffix tha t ties this group together.
1113 * .arg	i	- index into audit_pcbs[ ].
1114 * .ret	void.
1115 */
1116void
1117proc_pcb(audit_pcb_t *pcb, char *suffix, int i)
1118{
1119	if (suffix)
1120		pcb->pcb_suffix = suffix;
1121	pcbnum++;	/* one more pcb in use */
1122	pcb->pcb_size = AUDITBUFSIZE;
1123	pcb->pcb_rec = (char *)a_calloc(1, AUDITBUFSIZE);
1124	pcb->pcb_time = -1;
1125	pcb->pcb_flags |= PF_USEFILE;	/* note this one controls files */
1126	pcb->pcb_procno = i;	/* save index into audit_pcbs [] for id */
1127}
1128
1129
1130/*
1131 * .func	proc_label - process label range argument.
1132 * .desc	Parse label range lower-bound[;upper-bound]
1133 * .call	ret = proc_label(optstr).
1134 * .arg	opstr	- ptr to label range string
1135 * .ret 0	- no errors detected.
1136 * .ret -1	- errors detected (error_str set).
1137 */
1138
1139int
1140proc_label(char *optstr)
1141{
1142	char	*p;
1143	int	error;
1144
1145	if (flags & M_LABEL) {
1146		error_str = gettext("'l' option specified multiple times");
1147		return (-1);
1148	}
1149	flags |= M_LABEL;
1150
1151	if ((m_label = malloc(sizeof (m_range_t))) == NULL) {
1152		return (-1);
1153	}
1154	m_label->lower_bound = NULL;
1155	m_label->upper_bound = NULL;
1156
1157	p = strchr(optstr, ';');
1158	if (p == NULL) {
1159		/* exact label match, lower and upper range bounds the same */
1160		if (str_to_label(optstr, &m_label->lower_bound, MAC_LABEL,
1161		    L_NO_CORRECTION, &error) == -1) {
1162			(void) sprintf(errbuf,
1163			    gettext("invalid sensitivity label (%s) err %d"),
1164			    optstr, error);
1165			error_str = errbuf;
1166			goto errout;
1167		}
1168		m_label->upper_bound = m_label->lower_bound;
1169		return (0);
1170	}
1171	if (p == optstr) {
1172		/* lower bound is not specified .. default is admin_low */
1173		if (str_to_label(ADMIN_LOW, &m_label->lower_bound, MAC_LABEL,
1174		    L_NO_CORRECTION, &error) == -1) {
1175			goto errout;
1176		}
1177
1178		p++;
1179		if (*p == '\0') {
1180			/* upper bound not specified .. default is admin_high */
1181			if (str_to_label(ADMIN_HIGH, &m_label->upper_bound,
1182			    MAC_LABEL, L_NO_CORRECTION, &error) == -1) {
1183				goto errout;
1184			}
1185		} else {
1186			if (str_to_label(p, &m_label->upper_bound, MAC_LABEL,
1187			    L_NO_CORRECTION, &error) == -1) {
1188				(void) sprintf(errbuf, gettext(
1189				    "invalid sensitivity label (%s) err %d"),
1190				    p, error);
1191				error_str = errbuf;
1192				goto errout;
1193			}
1194		}
1195		return (0);
1196	}
1197	*p++ = '\0';
1198	if (str_to_label(optstr, &m_label->lower_bound, MAC_LABEL,
1199	    L_NO_CORRECTION, &error) == -1) {
1200		(void) sprintf(errbuf,
1201		    gettext("invalid sensitivity label (%s) err %d"), optstr,
1202		    error);
1203		error_str = errbuf;
1204		goto errout;
1205	}
1206	if (*p == '\0') {
1207		/* upper bound is not specified .. default is admin_high */
1208		if (str_to_label(ADMIN_HIGH, &m_label->upper_bound,
1209		    MAC_LABEL, L_NO_CORRECTION, &error) == -1) {
1210			goto errout;
1211		}
1212	} else {
1213		if (str_to_label(p, &m_label->upper_bound, MAC_LABEL,
1214		    L_NO_CORRECTION, &error) == -1) {
1215			(void) sprintf(errbuf,
1216			    gettext("invalid sensitivity label (%s) err %d"),
1217			    p, error);
1218			error_str = errbuf;
1219			goto errout;
1220		}
1221	}
1222	/* make sure that upper bound dominates the lower bound */
1223	if (!bldominates(m_label->upper_bound, m_label->lower_bound)) {
1224		*--p = ';';
1225		(void) sprintf(errbuf,
1226		    gettext("invalid sensitivity label range (%s)"), optstr);
1227		error_str = errbuf;
1228		goto errout;
1229	}
1230	return (0);
1231
1232errout:
1233	m_label_free(m_label->upper_bound);
1234	m_label_free(m_label->lower_bound);
1235	free(m_label);
1236
1237	return (-1);
1238}
1239
1240/*
1241 * proc_zonename - pick up zone name.
1242 *
1243 * all non-empty and not-too-long strings are valid since any name
1244 * may be valid.
1245 *
1246 * ret 0:	non-empty string
1247 * ret -1:	empty string or string is too long.
1248 */
1249static int
1250proc_zonename(char *optstr)
1251{
1252	size_t	length = strlen(optstr);
1253	if ((length < 1) || (length > ZONENAME_MAX)) {
1254		(void) sprintf(errbuf,
1255		    gettext("invalid zone name: %s"), optstr);
1256		error_str = errbuf;
1257		return (-1);
1258	}
1259	zonename = strdup(optstr);
1260	flags |= M_ZONENAME;
1261	return (0);
1262}
1263
1264/*
1265 * proc_frmi - set up frmi for pattern matching.
1266 *	Logic ripped off of scf_walk_fmri()
1267 *		Thanks to the smf team.
1268 *
1269 * ret 0:	OK
1270 * ret -1:	error
1271 */
1272static int
1273proc_fmri(char *optstr)
1274{
1275	if (strpbrk(optstr, "*?[") != NULL) {
1276		/* have a pattern to glob for */
1277
1278		fmri.sp_type = PATTERN_GLOB;
1279		if (optstr[0] == '*' ||
1280		    (strlen(optstr) >= 4 && optstr[3] == ':')) {
1281			fmri.sp_arg = strdup(optstr);
1282		} else if ((fmri.sp_arg = malloc(strlen(optstr) + 6)) != NULL) {
1283			(void) snprintf(fmri.sp_arg, strlen(optstr) + 6,
1284			    "svc:/%s", optstr);
1285		}
1286	} else {
1287		fmri.sp_type = PATTERN_PARTIAL;
1288		fmri.sp_arg = strdup(optstr);
1289	}
1290	if (fmri.sp_arg == NULL)
1291		return (-1);
1292
1293	return (0);
1294}
1295