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