OpenSolaris_b135/cmd/auditreduce/proc.c

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Main processor for auditreduce.
 * Mproc() is the entry point for this module. It is the only visible
 * function in this module.
 */

#include <sys/types.h>
#include <locale.h>
#include <bsm/libbsm.h>
#include <bsm/audit.h>
#include "auditr.h"

extern int	write_header();
extern int	token_processing();

static void	asort();
static audit_pcb_t *aget();
static int	get_file();
static int	write_recs();
static int	get_recs();
static int	check_rec();
static void	check_order();
static int	check_header();
static int	get_record();

static char	empty_file_token[] = {
#ifdef _LP64
		AUT_OTHER_FILE64, /* token id */
		0, 0, 0, 0, 0, 0, 0, 0, /* seconds of time */
		0, 0, 0, 0, 0, 0, 0, 0, /* microseconds of time */
#else
		AUT_OTHER_FILE32, /* token id */
		0, 0, 0, 0, /* seconds of time */
		0, 0, 0, 0, /* microseconds of time */
#endif
		0, 0, /* length of path name */
};


/*
 * .func	mproc - main processor.
 * .desc	Mproc controls a single process's actions.
 *	First one record is retreived from each pcb. As they are retreived
 *	they are placed into a linked list sorted with oldest first. Then
 *	the first one from the list is written out and another record
 *	read in to replace it. The new record is placed into the list.
 *	This continues until the list is empty.
 * .call	ret = mproc(pcbr).
 * .arg	pcbr	- ptr to pcb for this process.
 * .ret	0	- no errors in processing.
 * .ret	-1	- errors in processing (message already printed).
 */
int
mproc(pcbr)
register audit_pcb_t *pcbr;
{
	int	i, ret, junk;
	int	nrecs = 0;		/* number of records read from stream */
	int	nprecs = 0;		/* number of records put to stream */
	register audit_pcb_t *pcb;
	audit_pcb_t *aget();
	void	asort();

#if AUDIT_PROC_TRACE
	(void) fprintf(stderr, "mproc: count %d lo %d hi %d\n",
	    pcbr->pcb_count, pcbr->pcb_lo, pcbr->pcb_hi);
#endif

	/*
	 * First load up a record from each input group.
	 */
	for (i = pcbr->pcb_lo; i <= pcbr->pcb_hi; i++) {
		pcb = &(pcbr->pcb_below[i]); /* get next PCB */
		while (pcb->pcb_time < 0) { /* while no active record ... */
			if ((ret = get_file(pcb)) == -1)
				break;		/*  no files - finished PCB */
			if (ret == -2)
				return (-1);	/* quit processing - failed */
			if (get_recs(pcb, &nrecs) == 0)
				asort(pcb);	/* got a rec - put in list */
		}
	}
	/*
	 * Now process all of the records.
	 */
	while ((pcb = aget()) != NULL) {	/* get oldest record */
		if (write_recs(pcbr, pcb, &nprecs))
			return (-1);
		while (pcb->pcb_time < 0) {	/* while we don't have a rec */
			if (pcb->pcb_fpr == NULL) {	/* no active file ... */
				if ((ret = get_file(pcb)) == -1)
					break;	/* no files - finished pcb */
				else if (ret == -2)
					return (-1);	/* quit - failed */
			}
			if (get_recs(pcb, &nrecs) == 0)
				asort(pcb);		/* put record in list */
		}
	}
	/*
	 * For root: write outfile header if no records were encountered.
	 * For non-root: write trailer to pipe and close pipe.
	 */
	if (pcbr->pcb_flags & PF_ROOT) {
		if (nprecs == 0) {
			if (write_header())	/* write header if no records */
				return (-1);
		}
	} else {
		pcb = &(pcbr->pcb_below[0]);	/* any old PCB will do */
		pcb->pcb_rec = empty_file_token;
		if (write_recs(pcbr, pcb, &junk))
			return (-1);
		if (fclose(pcbr->pcb_fpw) == EOF) {
			if (!f_quiet)
				(void) fprintf(stderr,
				    gettext("%s couldn't close pipe.\n"), ar);
		}
	}
	/*
	 * For root process tell how many records were written.
	 */
	if (f_verbose && (pcbr->pcb_flags & PF_ROOT)) {
		(void) fprintf(stderr,
		    gettext("%s %d record(s) total were written out.\n"),
			ar, nprecs);
	}
	return (0);
}


/*
 * Head of linked-list of pcbs - sorted by time - oldest first.
 */
static audit_pcb_t		*pcbls = NULL;

/*
 * .func	asort - audit sort.
 * .desc	Place a pcb in the list sorted by time - oldest first.
 * .call	asort(pcb);
 * .arg	pcb	- ptr to pcb to install in list.
 * .ret	void.
 */
static void
asort(pcb)
register audit_pcb_t *pcb;
{
	register audit_pcb_t *pcbc, *pcbp;
	extern audit_pcb_t *pcbls;	/* ptr to start of list */

	pcb->pcb_next = NULL;
	if (pcbls == NULL) {
		pcbls = pcb;		/* empty list */
		return;
	}
	pcbc = pcbls;			/* current pcb */
	pcbp = pcbls;			/* previous pcb */
	while (pcbc != NULL) {
		if (pcb->pcb_time < pcbc->pcb_time) {
			if (pcbp == pcbc) {
				pcb->pcb_next = pcbls;	/* new -> 1st in list */
				pcbls = pcb;
				return;
			}
			pcbp->pcb_next = pcb;
			pcb->pcb_next = pcbc;		/* new in the inside */
			return;
		}
		pcbp = pcbc;
		pcbc = pcbc->pcb_next;
	}
	pcbp->pcb_next = pcb;				/* new -> last */
}


/*
 * .func	aget - audit get.
 * .desc	Get the first pcb from the list. Pcb is removed from list, too.
 * .call	pcb = aget().
 * .arg	none.
 * .ret	pcb	- ptr to pcb that was the first.
 */
static audit_pcb_t *
aget()
{
	audit_pcb_t *pcbret;
	extern audit_pcb_t *pcbls;	/* ptr to start of list */

	if (pcbls == NULL)
		return (pcbls);		/* empty list */
	pcbret = pcbls;
	pcbls = pcbls->pcb_next;	/* 2nd becomes 1st */
	return (pcbret);
}


/*
 * .func	get_file - get a new file.
 * .desc	Get the next file from the pcb's list. Check the header to see
 *	if the file really is an audit file. If there are no more then
 *	quit. If a file open (fopen) fails because the system file table
 *	is full or the process file table is full then quit processing
 *	altogether.
 * .call	ret = get_file(pcb).
 * .arg	pcb	- pcb holding the fcb's (files).
 * .ret	0	- new file opened for processing.
 * .ret	-1	- no more files - pcb finished.
 * .ret	-2	- fatal error - quit processing.
 */
static int
get_file(pcb)
register audit_pcb_t *pcb;
{
	FILE *fp;
	audit_fcb_t *fcb;

	/*
	 * Process file list until a good one if found or empty.
	 */
	while (pcb->pcb_fpr == NULL) {
		if ((fcb = pcb->pcb_first) == NULL) {
			pcb->pcb_time = -1;
			return (-1);	/* pcb is all done */
		} else {
		/*
		 * If we are reading from files then open the next one.
		 */
			if (!f_stdin) {
				if ((fp = fopen(fcb->fcb_file, "r")) == NULL) {
					if (!f_quiet) {
						(void) sprintf(errbuf, gettext(
						"%s couldn't open:\n  %s"),
						ar, fcb->fcb_file);
						perror(errbuf);
					}
					/*
					 * See if file space is depleted.
					 * If it is then we quit.
					 */
					if (errno == ENFILE || errno == EMFILE)
					{
						return (-2);
					}
					pcb->pcb_first = fcb->fcb_next;
					continue;	/* try another file */
				}
			} else {
				/*
				 * Read from standard input.
				 */
				fp = stdin;
			}
			/*
			 * Check header of audit file.
			 */
			if (check_header(fp, fcb->fcb_name)) {
				if (!f_quiet) {
					(void) fprintf(stderr,
					    "%s %s:\n  %s.\n",
					    ar, error_str, fcb->fcb_file);
				}
				if (fclose(fp) == EOF) {
					if (!f_quiet) {
						(void) fprintf(stderr, gettext(
						"%s couldn't close %s.\n"),
						ar, fcb->fcb_file);
					}
				}
				pcb->pcb_first = fcb->fcb_next;
				continue;		/* try another file */
			}
			/*
			 * Found a good audit file.
			 * Initalize pcb for processing.
			 */
			pcb->pcb_first = fcb->fcb_next;
			pcb->pcb_cur = fcb;
			pcb->pcb_fpr = fp;
			pcb->pcb_nrecs = 0;
			pcb->pcb_nprecs = 0;
			pcb->pcb_otime = -1;
		}
	}
	return (0);
}


/*
 * .func	write_recs - write records.
 * .desc	Write record from a buffer to output stream. Keep an eye out
 *	for the first and last records of the root's output stream.
 * .call	ret = write_recs(pcbr, pcb, nprecs).
 * .arg	pcbr	- ptr to node pcb.
 * .arg	pcb		- ptr to pcb holding the stream.
 * .arg	nprecs	- ptr to the number of put records. Updated here.
 * .ret	0	- no errors detected.
 * .ret	-1	- error in writing. Quit processing.
 */
static int
write_recs(pcbr, pcb, nprecs)
register audit_pcb_t *pcbr, *pcb;
int	*nprecs;
{
	adr_t adr;
	char	id;
	int32_t	size;

	adrm_start(&adr, pcb->pcb_rec);
	(void) adrm_char(&adr, &id, 1);
	(void) adrm_int32(&adr, &size, 1);

	/*
	 * Scan for first record to be written to outfile.
	 * When we find it then write the header and
	 * save the time for the outfile name.
	 */
	if ((*nprecs)++ == 0) {
		if (pcbr->pcb_flags & PF_ROOT) {
			f_start = pcb->pcb_time;	/* save start time */
			if (write_header())
				return (-1);
		}
	}
	f_end = pcb->pcb_time;			/* find last record's time */
	pcb->pcb_time = -1;			/* disable just written rec */

	if ((fwrite(pcb->pcb_rec, sizeof (char), size, pcbr->pcb_fpw)) !=
			size) {
		if (pcbr->pcb_flags & PF_ROOT) {
			(void) sprintf(errbuf, gettext(
				"%s write failed to %s"),
				ar, f_outfile ? f_outfile : gettext("stdout"));
			perror(errbuf);
		} else {
			perror(gettext("auditreduce: write failed to pipe"));
		}
		return (-1);
	}
	free(pcb->pcb_rec);
	return (0);
}

/*
 * .func get_recs - get records.
 * .desc Get records from a stream until one passing the current selection
 *	criteria is found or the stream is emptied.
 * .call	ret = get_recs(pcb, nr).
 * .arg	pcb	- ptr to pcb that holds this stream.
 * .arg	nr	- ptr to number of records read. Updated by this routine.
 * .ret	0	- got a record.
 * .ret	-1	- stream is finished.
 */
static int
get_recs(pcb, nr)
register audit_pcb_t *pcb;
int	*nr;
{
	adr_t adr;
	time_t secs;
	int	tmp;
	int	ret, ret2;
	int	nrecs = 0;	/* count how many records read this call */
	int	getrec = TRUE;
	int	alldone = FALSE;
	char	header_type;
	short	e;
	char	*str;
#if AUDIT_FILE
	static void	get_trace();
#endif

	while (getrec) {
		ret = get_record(pcb->pcb_fpr, &pcb->pcb_rec,
			pcb->pcb_cur->fcb_name);
		if (ret > 0) {
			adrm_start(&adr, pcb->pcb_rec);

			/* get token id */
			(void) adrm_char(&adr, (char *)&header_type, 1);
			/* skip over byte count */
			(void) adrm_int32(&adr, (int32_t *)&tmp, 1);
			/* skip over version # */
			(void) adrm_char(&adr, (char *)&tmp, 1);
			/* skip over event id */
			(void) adrm_short(&adr, (short *)&e, 1);
			/* skip over event id modifier */
			(void) adrm_short(&adr, (short *)&tmp, 1);

			if (header_type == AUT_HEADER32) {
			    int32_t s, m;

			    /* get seconds */
			    (void) adrm_int32(&adr, (int32_t *)&s, 1);
			    /* get microseconds */
			    (void) adrm_int32(&adr, (int32_t *)&m, 1);
			    secs = (time_t)s;
			} else if (header_type == AUT_HEADER32_EX) {
			    int32_t s, m;
			    int32_t t, junk[4];	/* at_type + at_addr[4] */

			    /* skip type and ip address field */
			    (void) adrm_int32(&adr, (int32_t *)&t, 1);
			    (void) adrm_int32(&adr, (int32_t *)&junk[0], t/4);

			    /* get seconds */
			    (void) adrm_int32(&adr, (int32_t *)&s, 1);
			    /* get microseconds */
			    (void) adrm_int32(&adr, (int32_t *)&m, 1);
			    secs = (time_t)s;
			} else if (header_type == AUT_HEADER64) {
			    int64_t s, m;

			    /* get seconds */
			    (void) adrm_int64(&adr, (int64_t *)&s, 1);
			    /* get microseconds */
			    (void) adrm_int64(&adr, (int64_t *)&m, 1);
#if ((!defined(_LP64)) || defined(_SYSCALL32))
			    if (s < (time_t)INT32_MIN ||
				s > (time_t)INT32_MAX)
					secs = 0;
			    else
					secs = (time_t)s;
#else
			    secs = (time_t)s;
#endif
			} else if (header_type == AUT_HEADER64_EX) {
			    int64_t s, m;
			    int32_t t, junk[4];

			    /* skip type and ip address field */
			    (void) adrm_int32(&adr, (int32_t *)&t, 1);
			    (void) adrm_int32(&adr, (int32_t *)&junk[0], t/4);

			    /* get seconds */
			    (void) adrm_int64(&adr, (int64_t *)&s, 1);
			    /* get microseconds */
			    (void) adrm_int64(&adr, (int64_t *)&m, 1);
#if ((!defined(_LP64)) || defined(_SYSCALL32))
			    if (s < (time_t)INT32_MIN ||
				s > (time_t)INT32_MAX)
					secs = 0;
			    else
					secs = (time_t)s;
#else
			    secs = (time_t)s;
#endif
			}
		}

#if AUDIT_REC
		(void) fprintf(stderr, "get_recs: %d ret %d recno %d\n",
			pcb->pcb_procno, ret, pcb->pcb_nrecs + 1);
#endif
		/*
		 * See if entire file is after the time window specified.
		 * Must be check here because the start time of the file name
		 * may be after the first record(s).
		 */
		if (pcb->pcb_nrecs == 0 && (pcb->pcb_flags & PF_USEFILE)) {
			/*
			 * If the first record read failed then use the time
			 * that was in the filename to judge.
			 */
			if (ret > 0)
				(pcb->pcb_cur)->fcb_start = secs;
			if (!f_all && (m_before <= (pcb->pcb_cur)->fcb_start)) {
				(void) fclose(pcb->pcb_fpr); /* ignore file */
				pcb->pcb_fpr = NULL;
				pcb->pcb_time = -1;
				return (-1);
			} else {
				/* Give belated announcement of file opening. */
				if (f_verbose) {
					(void) fprintf(stderr,
						gettext("%s opened:\n  %s.\n"),
						ar, (pcb->pcb_cur)->fcb_file);
				}
			}
		}
		/* Succesful acquisition of a record.  */
		if (ret > 0) {
			pcb->pcb_time = secs;	/* time of record */
			pcb->pcb_nrecs++;	/* # of read recs from stream */
			nrecs++;		/* # of recs read this call */
			/* Only check record if at bottom of process tree. */
			if (pcb->pcb_flags & PF_USEFILE) {
				check_order(pcb); /* check time sequence */
				if ((ret2 = check_rec(pcb)) == 0) {
					pcb->pcb_nprecs++;
					getrec = FALSE;
				} else if (ret2 == -2) {
					/* error */
					getrec = FALSE;	/* get no more recs */
					alldone = TRUE;	/* quit this file */
					free(pcb->pcb_rec);
				} else {
					/* -1: record not interesting */
					free(pcb->pcb_rec);
				}
			} else {
				pcb->pcb_nprecs++;
				getrec = FALSE;
			}
		} else {
			/* Error with record read or all done with stream. */
			getrec = FALSE;
			alldone = TRUE;
		}
	}
	if (alldone == TRUE) {
#if AUDIT_FILE
		get_trace(pcb);
#endif
		/* Error in record read. Display messages. */
		if (ret < 0 || ret2 == -2) {
			pcb->pcb_nrecs++;	/* # of read records */
			if (!f_quiet) {
				if (pcb->pcb_flags & PF_USEFILE) {
					/* Ignore if this is not_terminated. */
					if (!strstr((pcb->pcb_cur)->fcb_file,
							"not_terminated")) {
(void) fprintf(stderr, gettext("%s read error in %s at record %d.\n"), ar,
	(pcb->pcb_cur)->fcb_file, pcb->pcb_nrecs);
					}
				} else {
(void) fprintf(stderr, gettext("%s read error in pipe at record %d.\n"), ar,
	pcb->pcb_nrecs);
				}
			}
		} else {
			/*
			 * Only mark infile for deleting if we have succesfully
			 * processed all of it.
			 */
			if (pcb->pcb_flags & PF_USEFILE)
				(pcb->pcb_cur)->fcb_flags |= FF_DELETE;
		}
		if (fclose(pcb->pcb_fpr) == EOF) {
			if (!f_quiet) {
				if (pcb->pcb_flags & PF_USEFILE) {
					str = (pcb->pcb_cur)->fcb_file;
				} else {
					str = "pipe";
				}
				(void) fprintf(stderr,
					gettext("%s couldn't close %s.\n"),
					ar, str);
			}
		}
		pcb->pcb_fpr = NULL;
		pcb->pcb_time = -1;
		*nr += nrecs;
		return (-1);
	}
	*nr += nrecs;
	return (0);
}


#if AUDIT_FILE
/*
 * .func get_trace - get trace.
 * .desc If we are tracing file action (AUDIT_FILE is on) then print out
 *	a message when the file is closed regarding how many records
 *	were handled.
 * .call	get_trace(pcb).
 * .arg	pcb	- ptr to pcb holding file/pipe.
 * .ret	void.
 */
static void
get_trace(pcb)
audit_pcb_t *pcb;
{
	/*
	 * For file give filename, too.
	 */
	if (pcb->pcb_flags & PF_USEFILE) {
	(void) fprintf(stderr, "%s closed %s: %d records read recs: \
		%d record written.\n", ar, (pcb->pcb_cur)->fcb_file,
		pcb->pcb_nrecs, pcb->pcb_nprecs);
	} else {
		(void) fprintf(stderr, "%s closed pipe: %d records read: \
			%d records written .\n", ar, pcb->pcb_nrecs,
			pcb->pcb_nprecs);
	}
}

#endif

/*
 * .func	check_rec - check a record.
 * .desc	Check a record against the user's selection criteria.
 * .call	ret = check_rec(pcb).
 * .arg	pcb	- ptr to pcb holding the record.
 * .ret	0	- record accepted.
 * .ret	-1	- record rejected - continue processing file.
 * .ret	-2	- record rejected - quit processing file.
 */
static int
check_rec(pcb)
register audit_pcb_t *pcb;
{
	adr_t adr;
	struct timeval tv;
	uint_t	bytes;
	au_emod_t id_modifier;
	char	version;
	au_event_t event_type;
	char	tokenid;
	int	rc;	 /* return code */

	adrm_start(&adr, pcb->pcb_rec);
	(void) adrm_char(&adr, &tokenid, 1);

	/*
	 * checkflags will be my data structure for determining if
	 * a record has met ALL the selection criteria.  Once
	 * checkflags == flags, we have seen all we need to of the
	 * record, and can go to the next one.  If when we finish
	 * processing the record we still have stuff to see,
	 * checkflags != flags, and thus we should return a -1
	 * from this function meaning reject this record.
	 */

	checkflags = 0;

	/* must be header token -- sanity check */
	if (tokenid != AUT_HEADER32 && tokenid != AUT_HEADER64 &&
	    tokenid != AUT_HEADER32_EX && tokenid != AUT_HEADER64_EX) {
#if AUDIT_REC
		(void) fprintf(stderr,
		    "check_rec: %d recno %d no header %d found\n",
		    pcb->pcb_procno, pcb->pcb_nrecs, tokenid);
#endif
		return (-2);
	}

	/*
	 * The header token is:
	 *	attribute id:		char
	 *	byte count:		int
	 *	version #:		char
	 *	event ID:		short
	 *	ID modifier:		short
	 *	seconds (date):		int
	 *	time (microsecs):	int
	 */
	(void) adrm_u_int32(&adr, (uint32_t *)&bytes, 1);
	(void) adrm_char(&adr, &version, 1);
	(void) adrm_u_short(&adr, &event_type, 1);

	/*
	 * Used by s5_IPC_token to set the ipc_type so
	 * s5_IPC_perm_token can test.
	 */
	ipc_type = (char)0;

	if (flags & M_TYPE) {
		checkflags |= M_TYPE;
		if (m_type != event_type)
			return (-1);
	}
	if (flags & M_CLASS) {
		au_event_ent_t *ev = NULL;

		checkflags |= M_CLASS;
		if (cacheauevent(&ev, event_type) <= 0) {
		    (void) fprintf(stderr, gettext(
			"Warning: invalid event no %d in audit trail."),
			event_type);
		    return (-1);
		}
		global_class = ev->ae_class;
		if (!(flags & M_SORF) && !(mask.am_success & global_class))
			return (-1);
	}

	(void) adrm_u_short(&adr, &id_modifier, 1);

	/*
	 * Check record against time criteria.
	 * If the 'A' option was used then no time checking is done.
	 * The 'a' parameter is inclusive and the 'b' exclusive.
	 */
	if (tokenid == AUT_HEADER32) {
	    int32_t secs, msecs;
	    (void) adrm_int32(&adr, (int32_t *)&secs, 1);
	    (void) adrm_int32(&adr, (int32_t *)&msecs, 1);
	    tv.tv_sec = (time_t)secs;
	    tv.tv_usec = (suseconds_t)msecs;
	} else if (tokenid == AUT_HEADER32_EX) {
	    int32_t secs, msecs;
	    int32_t t, junk[5];	/* at_type + at_addr[4] */
	    /* skip type and ip address field */
	    (void) adrm_int32(&adr, (int32_t *)&t, 1);
	    (void) adrm_int32(&adr, (int32_t *)&junk[0], t/4);
	    /* get time */
	    (void) adrm_int32(&adr, (int32_t *)&secs, 1);
	    (void) adrm_int32(&adr, (int32_t *)&msecs, 1);
	    tv.tv_sec = (time_t)secs;
	    tv.tv_usec = (suseconds_t)msecs;
	} else if (tokenid == AUT_HEADER64) {
	    int64_t secs, msecs;
	    (void) adrm_int64(&adr, (int64_t *)&secs, 1);
	    (void) adrm_int64(&adr, (int64_t *)&msecs, 1);
#if ((!defined(_LP64)) || defined(_SYSCALL32))
	    if (secs < (time_t)INT32_MIN ||
		secs > (time_t)INT32_MAX)
			tv.tv_sec = 0;
	    else
			tv.tv_sec = (time_t)secs;
	    if (msecs < (suseconds_t)INT32_MIN ||
		msecs > (suseconds_t)INT32_MAX)
			tv.tv_usec = 0;
	    else
			tv.tv_usec = (suseconds_t)msecs;
#else
	    tv.tv_sec = (time_t)secs;
	    tv.tv_usec = (suseconds_t)msecs;
#endif
	} else if (tokenid == AUT_HEADER64_EX) {
	    int64_t secs, msecs;
	    int32_t t, junk[4];	/* at_type + at_addr[4] */
	    /* skip type and ip address field */
	    (void) adrm_int32(&adr, (int32_t *)&t, 1);
	    (void) adrm_int32(&adr, (int32_t *)&junk[0], t/4);
	    /* get time */
	    (void) adrm_int64(&adr, (int64_t *)&secs, 1);
	    (void) adrm_int64(&adr, (int64_t *)&msecs, 1);
#if ((!defined(_LP64)) || defined(_SYSCALL32))
	    if (secs < (time_t)INT32_MIN ||
		secs > (time_t)INT32_MAX)
			tv.tv_sec = 0;
	    else
			tv.tv_sec = (time_t)secs;
	    if (msecs < (suseconds_t)INT32_MIN ||
		msecs > (suseconds_t)INT32_MAX)
			tv.tv_usec = 0;
	    else
			tv.tv_usec = (suseconds_t)msecs;
#else
	    tv.tv_sec = (time_t)secs;
	    tv.tv_usec = (suseconds_t)msecs;
#endif
	}
	pcb->pcb_otime = pcb->pcb_time;
	if (!f_all) {
		if (m_after > tv.tv_sec)
			return (-1);
		if (m_before <= tv.tv_sec)
			return (-1);
	}

	/* if no selection flags were passed, select everything */
	if (!flags)
		return (0);

	/*
	 * If all information can be found in header,
	 * there is no need to continue processing the tokens.
	 */
	if (flags == checkflags)
		return (0);

	/*
	 * Process tokens until we hit the end of the record
	 */
	while ((uint_t)(adr.adr_now - adr.adr_stream) < bytes) {
		adrm_char(&adr, &tokenid, 1);
		rc = token_processing(&adr, tokenid);

		/* Any Problems? */
		if (rc == -2) {
			(void) fprintf(stderr,
			    gettext("auditreduce: bad token %u, terminating "
			    "file %s\n"), tokenid, (pcb->pcb_cur)->fcb_file);
			return (-2);
		}

		/* Are we finished? */
		if (flags == checkflags)
			return (0);
	}

	/*
	 * So, we haven't seen all that we need to see.  Reject record.
	 */

	return (-1);
}


/*
 * .func check_order - Check temporal sequence.
 * .call check_order(pcb).
 * .arg	 pcb - ptr to audit_pcb_t.
 * .desc	Check to see if the records are out of temporal sequence, ie,
 *	a record has a time stamp older than its predecessor.
 *	Also check to see if the current record is within the bounds of
 *	the file itself.
 *	This routine prints a diagnostic message, unless the QUIET
 *	option was selected.
 * .call	check_order(pcb).
 * .arg	pcb	- ptr to pcb holding the records.
 * .ret	void.
 */
static void
check_order(pcb)
register audit_pcb_t *pcb;
{
	char	cptr1[28], cptr2[28];	/* for error reporting */

	/*
	 * If the record-past is not the oldest then say so.
	 */
	if (pcb->pcb_otime > pcb->pcb_time) {
		if (!f_quiet) {
			(void) memcpy((void *)cptr1,
				(void *)ctime(&pcb->pcb_otime), 26);
			cptr1[24] = ' ';
			(void) memcpy((void *)cptr2,
				(void *)ctime(&pcb->pcb_time), 26);
			cptr2[24] = ' ';
			(void) fprintf(stderr,
	gettext("%s %s had records out of order: %s was followed by %s.\n"),
				ar, (pcb->pcb_cur)->fcb_file, cptr1, cptr2);
		}
	}
}


/*
 * .func	check_header.
 * .desc	Read in and check the header for an audit file.
 *	The header must read-in properly and have the magic #.
 * .call	err = check_header(fp).
 * .arg	fp	- file stream.
 * .ret	0	no problems.
 * .ret	-1	problems.
 */
static int
check_header(fp, fn)
FILE *fp;
char	*fn;
{
	char	id;
	char	*fname;
	short	pathlength;
	adr_t	adr;
	adrf_t	adrf;

	adrf_start(&adrf, &adr, fp);

	if (adrf_char(&adrf, &id, 1)) {
		(void) sprintf(errbuf, gettext("%s is empty"), fn);
		error_str = errbuf;
		return (-1);
	}
	if (!(id == AUT_OTHER_FILE32 || id == AUT_OTHER_FILE64)) {
		(void) sprintf(errbuf, gettext("%s not an audit file "), fn);
		error_str = errbuf;
		return (-1);
	}

	if (id == AUT_OTHER_FILE32) {
	    int32_t secs, msecs;
	    (void) adrf_int32(&adrf, (int32_t *)&secs, 1);
	    (void) adrf_int32(&adrf, (int32_t *)&msecs, 1);
	} else {
	    int64_t secs, msecs;
	    (void) adrf_int64(&adrf, (int64_t *)&secs, 1);
	    (void) adrf_int64(&adrf, (int64_t *)&msecs, 1);
#if ((!defined(_LP64)) || defined(_SYSCALL32))
	    if (secs < (time_t)INT32_MIN ||
		secs > (time_t)INT32_MAX) {
		    error_str = gettext("bad time stamp in file header");
		    return (-1);
	    }
	    if (msecs < (suseconds_t)INT32_MIN ||
		msecs > (suseconds_t)INT32_MAX) {
		    error_str = gettext("bad time stamp in file header");
		    return (-1);
	    }
#endif
	}

	if (adrf_short(&adrf, &pathlength, 1)) {
		error_str = gettext("incomplete file header");
		return (-1);
	}

	if (pathlength != 0) {
		fname = (char *)a_calloc(1, (size_t)pathlength);
		if ((fread(fname, sizeof (char), pathlength, fp)) !=
				pathlength) {
			(void) sprintf(errbuf,
				gettext("error in header/filename read in %s"),
				fn);
			error_str = errbuf;
			return (-1);
		}
		free(fname);
	}
	return (0);
}


/*
 * .func	get_record - get a single record.
 * .desc	Read a single record from stream fp. If the record to be read
 *	is larger than the buffer given to hold it (as determined by
 *	cur_size) then free that buffer and allocate a new and bigger
 *	one, making sure to store its size.
 * .call	ret = get_record(fp, buf, cur_size, flags).
 * .arg	fp	- stream to read from.
 * .arg	buf	- ptr to ptr to buffer to place record in.
 * .arg	cur_size- ptr to the size of the buffer that *buf points to.
 * .arg	flags	- flags from fcb (to get FF_NOTTERM).
 * .ret	+number	- number of chars in the record.
 * .ret	0	- trailer seen - file done.
 * .ret	-1	- read error (error_str know what type).
 */
static int
get_record(fp, buf, fn)
FILE *fp;
char	**buf;
char	*fn;
{
	adr_t	adr;
	adrf_t	adrf;
	int	leadin;
	char	id;
	int	lsize;
	short	ssize;

	/*
	 * Get the token type. It will be either a header or a file
	 * token.
	 */
	(void) adrf_start(&adrf, &adr, fp);
	if (adrf_char(&adrf, &id, 1)) {
		(void) sprintf(errbuf, gettext(
			"record expected but not found in %s"),
			fn);
		error_str = errbuf;
		return (-1);
	}
	switch (id) {
	case AUT_HEADER32:
	case AUT_HEADER32_EX:
	case AUT_HEADER64:
	case AUT_HEADER64_EX:
		/*
		 * The header token is:
		 *	attribute id:		char
		 *	byte count:		int
		 *	version #:		char
		 *	event ID:		short
		 *	ID modifier:		short
		 *	IP address type		int	(_EX only)
		 *	IP address		1/4*int (_EX only)
		 *	seconds (date):		long
		 *	time (microsecs):	long
		 */
		leadin = sizeof (int32_t) + sizeof (char);
		(void) adrf_int32(&adrf, &lsize, 1);
		*buf = (char *)a_calloc(1, (size_t)(lsize + leadin));
		adr_start(&adr, *buf);
		adr_char(&adr, &id, 1);
		adr_int32(&adr, (int32_t *)&lsize, 1);
		if (fread(*buf + leadin, sizeof (char), lsize - leadin, fp) !=
			lsize - leadin) {
			(void) sprintf(errbuf,
				gettext("header token read failure in %s"), fn);
			error_str = errbuf;
			return (-1);
		}
		return (lsize + leadin);
	case AUT_OTHER_FILE32: {
		int32_t secs, msecs;
		leadin =  2 * sizeof (int32_t) +
				sizeof (short) + sizeof (char);
		(void) adrf_int32(&adrf, (int32_t *)&secs, 1);
		(void) adrf_int32(&adrf, (int32_t *)&msecs, 1);
		(void) adrf_short(&adrf, &ssize, 1);
		*buf = (char *)a_calloc(1, (size_t)(ssize + leadin));
		adr_start(&adr, *buf);
		adr_char(&adr, &id, 1);
		adr_int32(&adr, (int32_t *)&secs, 1);
		adr_int32(&adr, (int32_t *)&msecs, 1);
		adr_short(&adr, &ssize, 1);
		if (fread(*buf + leadin, sizeof (char), ssize, fp) != ssize) {
			error_str = gettext("file token read failure");
			return (-1);
		}
		return (0);		/* done! */
	}
	case AUT_OTHER_FILE64: {
		int64_t secs, msecs;
		leadin =  2 * sizeof (int64_t) +
				sizeof (short) + sizeof (char);
		(void) adrf_int64(&adrf, (int64_t *)&secs, 1);
		(void) adrf_int64(&adrf, (int64_t *)&msecs, 1);
		(void) adrf_short(&adrf, &ssize, 1);
		*buf = (char *)a_calloc(1, (size_t)(ssize + leadin));
		adr_start(&adr, *buf);
		adr_char(&adr, &id, 1);
		adr_int64(&adr, (int64_t *)&secs, 1);
		adr_int64(&adr, (int64_t *)&msecs, 1);
		adr_short(&adr, &ssize, 1);
		if (fread(*buf + leadin, sizeof (char), ssize, fp) != ssize) {
			error_str = gettext("file token read failure");
			return (-1);
		}
		return (0);		/* done! */
	}
	default:
		break;
	}
	error_str = gettext("record begins without proper token");
	return (-1);
}