OpenSolaris_b135/cmd/acct/wtmpfix.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * wtmpfix - adjust wtmpx file and remove date changes.
 *	wtmpfix <wtmpx1 >wtmpx2
 *
 *	Can recover to some extent from wtmpx corruption.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include "acctdef.h"
#include <utmpx.h>
#include <time.h>
#include <ctype.h>
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define	DAYEPOCH	(60 * 60 * 24)
#define	UTRSZ		(sizeof (struct futmpx)) /* file record size */

/*
 * The acctsh(1M) shell scripts startup(1M) and shutacct(1M) as well as the
 * runacct script each pass their own specific reason strings in the first
 * argument to acctwtmp(1M), to be propagated into ut_line fields.  Additional
 * reasons (RUNLVL_MSG, ..., DOWN_MSG), used by compiled code, are defined in
 * <utmp.h> as preprocessor constants.
 * For simplicity we predefine similar constants for the scripted strings
 * here, as no other compiled code uses those.
 * Moreover, we need a variant of RUNLVL_MSG without the "%c" at the end.
 * We shall use the fact that ut_line[RLVLMSG_LEN] will extract the char
 * in the %c position ('S', '2', ...).
 * Since all of these string constants are '\0' terminated, they can safely
 * be used with strcmp() even when ut_line is not.
 */
#define	RUN_LEVEL_MSG	"run-level "
#define	ACCTG_ON_MSG	"acctg on"
#define	ACCTG_OFF_MSG	"acctg off"
#define	RUNACCT_MSG	"runacct"

#define	RLVLMSG_LEN	(sizeof (RUN_LEVEL_MSG) - 1)

/*
 * Records encountered are classified as one of the following:  corrupted;
 * ok but devoid of interest to acctcon downstream;  ok and interesting;
 * or ok and even redundant enough to latch onto a new alignment whilst
 * recovering from a corruption.
 * The ordering among these four symbolic values is significant.
 */
typedef enum {
	INRANGE_ERR = -1,
	INRANGE_DROP,
	INRANGE_PASS,
	INRANGE_ALIGNED
} inrange_t;

/* input filenames and record numbers, for diagnostics only */
#define	STDIN_NAME	"<stdin>"
static char	*cur_input_name;
static off_t	recin;

static FILE	*Wtmpx, *Temp;

struct	dtab
{
	off_t	d_off1;		/* file offset start */
	off_t	d_off2;		/* file offset stop */
	time_t	d_adj;		/* time adjustment */
	struct dtab *d_ndp;	/* next record */
};

static struct	dtab	*Fdp;	/* list header */
static struct	dtab	*Ldp;	/* list trailer */

static time_t 	lastmonth, nextmonth;

static struct	futmpx	Ut, Ut2;

static int winp(FILE *, struct futmpx *);
static void mkdtab(off_t);
static void setdtab(off_t, struct futmpx *, struct futmpx *);
static void adjust(off_t, struct futmpx *);
static int invalid(char *);
static void scanfile(void);
static inrange_t inrange(void);
static void wcomplain(char *);

int
main(int argc, char **argv)
{
	time_t tloc;
	struct tm *tmp;
	int year;
	int month;
	off_t rectmpin;

	(void) setlocale(LC_ALL, "");
	setbuf(stdout, NULL);

	(void) time(&tloc);
	tmp = localtime(&tloc);
	year = tmp->tm_year;
	month = tmp->tm_mon + 1;
	lastmonth = ((year + 1900 - 1970) * 365 +
	    (month - 1) * 30) * DAYEPOCH;
	nextmonth = ((year + 1900 - 1970) * 365 +
	    (month + 1) * 30) * DAYEPOCH;

	if (argc < 2) {
		argv[argc] = "-";
		argc++;
	}

	/*
	 * Almost all system call failures in this program are unrecoverable
	 * and therefore fatal.  Typical causes might be lack of memory or
	 * of space in a filesystem.  If necessary, the system administrator
	 * can invoke /usr/lib/acct/runacct interactively after making room
	 * to complete the remaining phases of last night's accounting.
	 */
	if ((Temp = tmpfile()) == NULL) {
		perror("Cannot create temporary file");
		return (EXIT_FAILURE);
	}

	while (--argc > 0) {
		argv++;
		if (strcmp(*argv, "-") == 0) {
			Wtmpx = stdin;
			cur_input_name = STDIN_NAME;
		} else if ((Wtmpx = fopen(*argv, "r")) == NULL) {
			(void) fprintf(stderr, "Cannot open %s: %s\n",
			    *argv, strerror(errno));
			return (EXIT_FAILURE);
		} else {
			cur_input_name = *argv;
		}
		/*
		 * Filter records reading from current input stream Wtmpx,
		 * writing to Temp.
		 */
		scanfile();

		if (Wtmpx != stdin)
			(void) fclose(Wtmpx);
	}
	/* flush and rewind Temp for readback */
	if (fflush(Temp) != 0) {
		perror("<temporary file>: fflush");
		return (EXIT_FAILURE);
	}
	if (fseeko(Temp, (off_t)0L, SEEK_SET) != 0) {
		perror("<temporary file>: seek");
		return (EXIT_FAILURE);
	}
	/* second pass: apply time adjustments */
	rectmpin = 0;
	while (winp(Temp, &Ut)) {
		adjust(rectmpin, &Ut);
		rectmpin += UTRSZ;
		if (fwrite(&Ut, UTRSZ, 1, stdout) < 1) {
			perror("<stdout>: fwrite");
			return (EXIT_FAILURE);
		}
	}
	(void) fclose(Temp);
	/*
	 * Detect if we've run out of space (say) and exit unsuccessfully
	 * so that downstream accounting utilities won't start processing an
	 * incomplete tmpwtmp file.
	 */
	if (fflush(stdout) != 0) {
		perror("<stdout>: fflush");
		return (EXIT_FAILURE);
	}
	return (EXIT_SUCCESS);
}

static int
winp(FILE *f, struct futmpx *w)
{
	if (fread(w, (size_t)UTRSZ, (size_t)1, f) != 1)
		return (0);
	if ((w->ut_type >= EMPTY) && (w->ut_type <= UTMAXTYPE))
		return (1);
	else {
		(void) fprintf(stderr, "Bad temp file at offset %lld\n",
		    (longlong_t)(ftell(f) - UTRSZ));
		/*
		 * If input was corrupt, neither ut_line nor ut_user can be
		 * relied on to be \0-terminated.  Even fixing the precision
		 * does not entirely guard against this.
		 */
		(void) fprintf(stderr,
		    "ut_line \"%-12.12s\" ut_user \"%-8.8s\" ut_xtime %ld\n",
		    w->ut_line, w->ut_user, (long)w->ut_xtime);
		exit(EXIT_FAILURE);
	}
	/* NOTREACHED */
}

static void
mkdtab(off_t p)
{

	struct dtab *dp;

	dp = Ldp;
	if (dp == NULL) {
		dp = calloc(sizeof (struct dtab), 1);
		if (dp == NULL) {
			(void) fprintf(stderr, "out of memory\n");
			exit(EXIT_FAILURE);
		}
		Fdp = Ldp = dp;
	}
	dp->d_off1 = p;
}

static void
setdtab(off_t p, struct futmpx *w1, struct futmpx *w2)
{
	struct dtab *dp;

	if ((dp = Ldp) == NULL) {
		(void) fprintf(stderr, "no dtab\n");
		exit(EXIT_FAILURE);
	}
	dp->d_off2 = p;
	dp->d_adj = w2->ut_xtime - w1->ut_xtime;
	if ((Ldp = calloc(sizeof (struct dtab), 1)) == NULL) {
		(void) fprintf(stderr, "out of memory\n");
		exit(EXIT_FAILURE);
	}
	Ldp->d_off1 = dp->d_off1;
	dp->d_ndp = Ldp;
}

static void
adjust(off_t p, struct futmpx *w)
{

	off_t pp;
	struct dtab *dp;

	pp = p;

	for (dp = Fdp; dp != NULL; dp = dp->d_ndp) {
		if (dp->d_adj == 0)
			continue;
		if (pp >= dp->d_off1 && pp <= dp->d_off2)
			w->ut_xtime += dp->d_adj;
	}
}

/*
 * invalid() determines whether the name field adheres to the criteria
 * set forth in acctcon1.  If returns VALID if the name is ok, or
 * INVALID if the name violates conventions.
 */

static int
invalid(char *name)
{
	int	i;

	for (i = 0; i < NSZ; i++) {
		if (name[i] == '\0')
			return (VALID);
		if (! (isalnum(name[i]) || (name[i] == '$') ||
		    (name[i] == ' ') || (name[i] == '.') ||
		    (name[i] == '_') || (name[i] == '-'))) {
			return (INVALID);
		}
	}
	return (VALID);
}

/*
 * scanfile:
 * 1)  	reads the current input file
 * 2)   filters for process records in time range of interest and for
 *      other types of records deemed interesting to acctcon downstream
 * 3)   picks up time changes with setdtab() if in multiuser mode, which
 *      will be applied when the temp file is read back
 * 4)   changes bad login names to INVALID
 * 5)   recovers from common cases of wtmpx corruption (loss of record
 *      alignment).
 * All of the static globals are used directly or indirectly.
 *
 * When wtmpfix is asked to process several input files in succession,
 * some state needs to be preserved from one scanfile() invocation to the
 * next.  Aside from the temp file position, we remember whether we were
 * in multi-user mode or not.  Absent evidence to the contrary, we begin
 * processing assuming multi-user mode, because runacct's wtmpx rotation
 * normally gives us a file recently initialized by utmp2wtmp(1M) with no
 * older RUN_LVL records surviving.
 */

static void
scanfile()
{
	struct stat Wtstat;
	off_t residue = 0;	/* input file size mod UTRSZ */
	/*
	 * lastok will be the offset of the beginning of the most recent
	 * manifestly plausible and interesting input record in the current
	 * input file, if any.
	 * An invariant at loop entry is -UTRSZ <= lastok <= recin - UTRSZ.
	 */
	off_t lastok = -(off_t)UTRSZ;
	static off_t rectmp;	/* current temp file position */
	static boolean_t multimode = B_TRUE; /* multi-user RUN_LVL in force */
	inrange_t is_ok;	/* caches inrange() result */
	/*
	 * During normal operation, records are of interest and copied to
	 * the output when is_ok >= INRANGE_PASS, ignored and dropped when
	 * is_ok == INRANGE_DROP, and evidence of corruption otherwise.
	 * While we are trying to recover from a corruption and hunting for
	 * records with sufficient redundancy to confirm that we have reached
	 * proper alignment again, we'll want is_ok >= INRANGE_ALIGNED.
	 * The value of want_ok is the minimum inrange() result of current
	 * interest.  It is raised to INRANGE_ALIGNED during ongoing recovery
	 * and dropped back to INRANGE_PASS when we have recovered alignment.
	 */
	inrange_t want_ok = INRANGE_PASS;
	boolean_t recovered = B_FALSE; /* true after a successful recovery */
	int n;

	if (fstat(fileno(Wtmpx), &Wtstat) == -1) {
		(void) fprintf(stderr,
		    "Cannot stat %s (will read sequentially): %s\n",
		    cur_input_name, strerror(errno));
	} else if ((Wtstat.st_mode & S_IFMT) == S_IFREG) {
		residue = Wtstat.st_size % UTRSZ;
	}

	/* if residue != 0, part of the file may be misaligned */
	for (recin = 0;
	    ((n = fread(&Ut, (size_t)UTRSZ, (size_t)1, Wtmpx)) > 0) ||
	    (residue > 0);
	    recin += UTRSZ) {
		if (n == 0) {
			/*
			 * Implying residue > 0 and want_ok == INRANGE_PASS.
			 * It isn't worth telling an I/O error from EOF here.
			 * But one case is worth catching to avoid issuing a
			 * confusing message below.  When the previous record
			 * had been ok, we just drop the current truncated
			 * record and bail out of the loop -- no seeking back.
			 */
			if (lastok == recin - UTRSZ) {
				wcomplain("file ends in mid-record, "
				    "final partial record dropped");
				break;
			} else {
				wcomplain("file ends in mid-record");
				/* handled below like a corrupted record */
				is_ok = INRANGE_ERR;
			}
		} else
			is_ok = inrange();

		/* alignment recovery logic */
		if ((residue > 0) && (is_ok == INRANGE_ERR)) {
			/*
			 * "Let's go back to the last place where we knew
			 * where we were..."
			 * In fact, if the last record had been fine and we
			 * know there's at least one whole record ahead, we
			 * might move forward here  (by residue bytes, less
			 * than one record's worth).  In any case, we align
			 * ourselves to an integral number of records before
			 * the end of the file.
			 */
			wcomplain("suspecting misaligned records, "
			    "repositioning");
			recin = lastok + UTRSZ + residue;
			residue = 0;
			if (fseeko(Wtmpx, recin, SEEK_SET) != 0) {
				(void) fprintf(stderr, "%s: seek: %s\n",
				    cur_input_name, strerror(errno));
				exit(EXIT_FAILURE);
			}
			wcomplain("starting re-scan");
			/*
			 * While want_ok is elevated, only unequivocal records
			 * with inrange() == INRANGE_ALIGNED will be admitted
			 * to latch onto the tentative new alignment.
			 */
			want_ok = INRANGE_ALIGNED;
			/*
			 * Compensate for the loop continuation.  Doing
			 * it this way gets the correct offset reported
			 * in the re-scan message above.
			 */
			recin -= UTRSZ;
			continue;
		}
		/* assert: residue == 0 or is_ok >= INRANGE_DROP here */
		if (is_ok < want_ok)
			/* record of no further interest */
			continue;
		if (want_ok == INRANGE_ALIGNED) {
			wcomplain("now recognizing aligned records again");
			want_ok = INRANGE_PASS;
			recovered = B_TRUE;
		}
		/*
		 * lastok must track recin whenever the current record is
		 * being processed and written out to our temp file, to avoid
		 * reprocessing any bits already done when we readjust our
		 * alignment.
		 */
		lastok = recin;

		/* now we have a good wtmpx record, do more processing */

		if (rectmp == 0 || Ut.ut_type == BOOT_TIME)
			mkdtab(rectmp);
		if (Ut.ut_type == RUN_LVL) {
			/* inrange() already checked the "run-level " part */
			if (Ut.ut_line[RLVLMSG_LEN] == 'S')
				multimode = B_FALSE;
			else if ((Ut.ut_line[RLVLMSG_LEN] == '2') ||
			    (Ut.ut_line[RLVLMSG_LEN] == '3') ||
			    (Ut.ut_line[RLVLMSG_LEN] == '4'))
				multimode = B_TRUE;
		}
		if (invalid(Ut.ut_name) == INVALID) {
			(void) fprintf(stderr,
			    "wtmpfix: logname \"%*.*s\" changed "
			    "to \"INVALID\"\n", OUTPUT_NSZ,
			    OUTPUT_NSZ, Ut.ut_name);
			(void) strncpy(Ut.ut_name, "INVALID", NSZ);
		}
		/*
		 * Special case: OLD_TIME should be immediately followed by
		 * NEW_TIME.
		 * We make no attempt at alignment recovery between these
		 * two: if there's junk at this point in the input, then
		 * a NEW_TIME seen after the junk probably won't be the one
		 * we are looking for.
		 */
		if (Ut.ut_type == OLD_TIME) {
			/*
			 * Make recin refer to the expected NEW_TIME.
			 * Loop continuation will increment it again
			 * for the record we're about to read now.
			 */
			recin += UTRSZ;
			if (!fread(&Ut2, (size_t)UTRSZ, (size_t)1, Wtmpx)) {
				wcomplain("input truncated after OLD_TIME - "
				    "giving up");
				exit(EXIT_FAILURE);
			}
			/*
			 * Rudimentary NEW_TIME sanity check.  Not as thorough
			 * as in inrange(), but then we have redundancy from
			 * context here, since we're just after a plausible
			 * OLD_TIME record.
			 */
			if ((Ut2.ut_type != NEW_TIME) ||
			    (strcmp(Ut2.ut_line, NTIME_MSG) != 0)) {
				wcomplain("NEW_TIME expected but missing "
				    "after OLD_TIME - giving up");
				exit(EXIT_FAILURE);
			}
			lastok = recin;
			if (multimode == B_TRUE)
				setdtab(rectmp, &Ut, &Ut2);
			rectmp += 2 * UTRSZ;
			if ((fwrite(&Ut, UTRSZ, 1, Temp) < 1) ||
			    (fwrite(&Ut2, UTRSZ, 1, Temp) < 1)) {
				perror("<temporary file>: fwrite");
				exit(EXIT_FAILURE);
			}
			continue;
		}
		if (fwrite(&Ut, UTRSZ, 1, Temp) < 1) {
			perror("<temporary file>: fwrite");
			exit(EXIT_FAILURE);
		}
		rectmp += UTRSZ;
	}
	if (want_ok == INRANGE_ALIGNED) {
		wcomplain("EOF reached without recognizing another aligned "
		    "record with certainty. This file may need to be "
		    "repaired by hand.\n");
	} else if (recovered == B_TRUE) {
		/*
		 * There may have been a number of wcomplain() messages
		 * since we reported about the re-scan, so it bears repeating
		 * at the end that not all was well.
		 */
		wcomplain("EOF reached after recovering from corruption "
		    "in the middle of the file.  This file may need to be "
		    "repaired by hand.\n");
	}
}

/*
 * inrange: inspect what we hope to be one wtmpx record.
 * Globals:  Ut, lastmonth, nextmonth;  recin, cur_input_name (diagnostics)
 * Return values:
 * INRANGE_ERR     -- an inconsistency was detected, input file corrupted
 * INRANGE_DROP    -- Ut appears consistent but isn't of interest
 *                    (of process type and outside the time range we want)
 * INRANGE_PASS    -- Ut appears consistent and this record is of interest
 * INRANGE_ALIGNED -- same, and it is also redundant enough to be sure
 *                    that we're correctly aligned on record boundaries
 */
#define	UNEXPECTED_UT_PID \
	(Ut.ut_pid != 0) || \
	(Ut.ut_exit.e_termination != 0) || \
	(Ut.ut_exit.e_exit != 0)

static inrange_t
inrange()
{
	/* pid_t is signed so that fork() can return -1.  Exploit this. */
	if (Ut.ut_pid < 0) {
		wcomplain("negative pid");
		return (INRANGE_ERR);
	}

	/* the legal values for ut_type are enumerated in <utmp.h> */
	switch (Ut.ut_type) {
	case EMPTY:
		if (UNEXPECTED_UT_PID) {
			wcomplain("nonzero pid or status in EMPTY record");
			return (INRANGE_ERR);
		}
		/*
		 * We'd like to have Ut.ut_user[0] == '\0' here, but sadly
		 * this isn't always so, so we can't rely on it.
		 */
		return (INRANGE_DROP);
	case RUN_LVL:
		/* ut_line must have come from the RUNLVL_MSG pattern */
		if (strncmp(Ut.ut_line, RUN_LEVEL_MSG, RLVLMSG_LEN) != 0) {
			wcomplain("RUN_LVL record doesn't say `"
			    RUN_LEVEL_MSG "'");
			return (INRANGE_ERR);
		}
		/*
		 * The ut_pid, termination, and exit status fields have
		 * special meaning in this case, and none of them is
		 * suitable for checking.  And we won't insist on ut_user
		 * to always be an empty string.
		 */
		return (INRANGE_ALIGNED);
	case BOOT_TIME:
		if (UNEXPECTED_UT_PID) {
			wcomplain("nonzero pid or status in BOOT_TIME record");
			return (INRANGE_ERR);
		}
		if (strcmp(Ut.ut_line, BOOT_MSG) != 0) {
			wcomplain("BOOT_TIME record doesn't say `"
			    BOOT_MSG "'");
			return (INRANGE_ERR);
		}
		return (INRANGE_ALIGNED);
	case OLD_TIME:
		if (UNEXPECTED_UT_PID) {
			wcomplain("nonzero pid or status in OLD_TIME record");
			return (INRANGE_ERR);
		}
		if (strcmp(Ut.ut_line, OTIME_MSG) != 0) {
			wcomplain("OLD_TIME record doesn't say `"
			    OTIME_MSG "'");
			return (INRANGE_ERR);
		}
		return (INRANGE_ALIGNED);
	case NEW_TIME:
		/*
		 * We don't actually expect to see any here.  If they follow
		 * an OLD_TIME record as they should, they'll be handled on
		 * the fly in scanfile().  But we might still run into one
		 * if the input is somehow corrupted.
		 */
		if (UNEXPECTED_UT_PID) {
			wcomplain("nonzero pid or status in NEW_TIME record");
			return (INRANGE_ERR);
		}
		if (strcmp(Ut.ut_line, NTIME_MSG) != 0) {
			wcomplain("NEW_TIME record doesn't say `"
			    NTIME_MSG "'");
			return (INRANGE_ERR);
		}
		return (INRANGE_ALIGNED);

	/* the four *_PROCESS ut_types have a lot in common */
	case USER_PROCESS:
		/*
		 * Catch two special cases first: psradm records have no id
		 * and no pid, while root login over FTP may not have a
		 * valid ut_user and may have garbage in ut_id[3].
		 */
		if ((strcmp(Ut.ut_user, "psradm") == 0) &&
		    (Ut.ut_id[0] == '\0') &&
		    (Ut.ut_pid > 0)) {
			if ((Ut.ut_xtime > lastmonth) &&
			    (Ut.ut_xtime < nextmonth)) {
				return (INRANGE_ALIGNED);
			} else {
				return (INRANGE_DROP);
			}
		}
		if ((Ut.ut_user[0] == '\0') &&
		    (strncmp(Ut.ut_id, "ftp", 3) == 0) &&
		    (strncmp(Ut.ut_line, "ftp", 3) == 0)) {
			if ((Ut.ut_xtime > lastmonth) &&
			    (Ut.ut_xtime < nextmonth)) {
				return (INRANGE_ALIGNED);
			} else {
				return (INRANGE_DROP);
			}
		}
		/* FALLTHROUGH */
	case LOGIN_PROCESS:
		if (Ut.ut_user[0] == '\0') {
			wcomplain("missing username in process record");
			return (INRANGE_ERR);
		}
		/* FALLTHROUGH */
	case INIT_PROCESS:
		/*
		 * INIT_PROCESS and DEAD_PROCESS records can come with an
		 * empty ut_user in degenerate cases (e.g. syntax errors
		 * like a comment-only process field in /etc/inittab).
		 * But in an INIT_PROCESS, LOGIN_PROCESS, or USER_PROCESS
		 * record, we expect a respectable ut_pid.
		 */
		if (Ut.ut_pid == 0) {
			wcomplain("null pid in process record");
			return (INRANGE_ERR);
		}
		/* FALLTHROUGH */
	case DEAD_PROCESS:
		/*
		 * DEAD_PROCESS records with a null ut_pid can be produced
		 * by gnome-terminal (normally seen in utmpx only, but they
		 * can leak into wtmpx in rare circumstances).
		 * Unfortunately, ut_id can't be relied on to contain
		 * anything in particular.  (E.g., sshd might leave it
		 * 0-initialized.)  This leaves almost no verifiable
		 * redundancy here beyond the ut_type.
		 * At least we insist on a reasonable timestamp.
		 */
		if (Ut.ut_xtime <= 0) {
			wcomplain("non-positive time in process record");
			return (INRANGE_ERR);
		}
		if ((Ut.ut_xtime > lastmonth) &&
		    (Ut.ut_xtime < nextmonth)) {
			return (INRANGE_PASS);
		} else {
			return (INRANGE_DROP);
		}
	case ACCOUNTING:
		/*
		 * If we recognize one of the three reason strings passed
		 * by the /usr/lib/acct shell scripts to acctwtmp, we
		 * exploit the available redundancy they offer.  But
		 * acctwtmp could have been invoked by custom scripts or
		 * interactively with other reason strings in the first
		 * argument, so anything we don't recognize does not
		 * constitute evidence for corruption.
		 */
		if ((strcmp(Ut.ut_line, RUNACCT_MSG) != 0) &&
		    (strcmp(Ut.ut_line, ACCTG_ON_MSG) != 0) &&
		    (strcmp(Ut.ut_line, ACCTG_OFF_MSG) != 0)) {
			return (INRANGE_DROP);
		}
		return (INRANGE_ALIGNED);
	case DOWN_TIME:
		if (UNEXPECTED_UT_PID) {
			wcomplain("nonzero pid or status in DOWN_TIME record");
			return (INRANGE_ERR);
		}
		if (strcmp(Ut.ut_line, DOWN_MSG) != 0) {
			wcomplain("DOWN_TIME record doesn't say `"
			    DOWN_MSG "'");
			return (INRANGE_ERR);
		}
		return (INRANGE_ALIGNED);
	default:
		wcomplain("ut_type out of range");
		return (INRANGE_ERR);
	}
	/* NOTREACHED */
}

static void
wcomplain(char *msg)
{
	(void) fprintf(stderr, "%s: offset %lld: %s\n", cur_input_name,
	    (longlong_t)recin, msg);
}