Home | History | Annotate | Download | only in acct
      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 (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
     23 /*	  All Rights Reserved  	*/
     24 
     25 /*
     26  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     27  * Use is subject to license terms.
     28  */
     29 
     30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     31 
     32 /*
     33  * wtmpfix - adjust wtmpx file and remove date changes.
     34  *	wtmpfix <wtmpx1 >wtmpx2
     35  *
     36  *	Can recover to some extent from wtmpx corruption.
     37  */
     38 
     39 #include <stdio.h>
     40 #include <sys/types.h>
     41 #include <sys/stat.h>
     42 #include <sys/param.h>
     43 #include "acctdef.h"
     44 #include <utmpx.h>
     45 #include <time.h>
     46 #include <ctype.h>
     47 #include <locale.h>
     48 #include <stdlib.h>
     49 #include <string.h>
     50 #include <errno.h>
     51 
     52 #define	DAYEPOCH	(60 * 60 * 24)
     53 #define	UTRSZ		(sizeof (struct futmpx)) /* file record size */
     54 
     55 /*
     56  * The acctsh(1M) shell scripts startup(1M) and shutacct(1M) as well as the
     57  * runacct script each pass their own specific reason strings in the first
     58  * argument to acctwtmp(1M), to be propagated into ut_line fields.  Additional
     59  * reasons (RUNLVL_MSG, ..., DOWN_MSG), used by compiled code, are defined in
     60  * <utmp.h> as preprocessor constants.
     61  * For simplicity we predefine similar constants for the scripted strings
     62  * here, as no other compiled code uses those.
     63  * Moreover, we need a variant of RUNLVL_MSG without the "%c" at the end.
     64  * We shall use the fact that ut_line[RLVLMSG_LEN] will extract the char
     65  * in the %c position ('S', '2', ...).
     66  * Since all of these string constants are '\0' terminated, they can safely
     67  * be used with strcmp() even when ut_line is not.
     68  */
     69 #define	RUN_LEVEL_MSG	"run-level "
     70 #define	ACCTG_ON_MSG	"acctg on"
     71 #define	ACCTG_OFF_MSG	"acctg off"
     72 #define	RUNACCT_MSG	"runacct"
     73 
     74 #define	RLVLMSG_LEN	(sizeof (RUN_LEVEL_MSG) - 1)
     75 
     76 /*
     77  * Records encountered are classified as one of the following:  corrupted;
     78  * ok but devoid of interest to acctcon downstream;  ok and interesting;
     79  * or ok and even redundant enough to latch onto a new alignment whilst
     80  * recovering from a corruption.
     81  * The ordering among these four symbolic values is significant.
     82  */
     83 typedef enum {
     84 	INRANGE_ERR = -1,
     85 	INRANGE_DROP,
     86 	INRANGE_PASS,
     87 	INRANGE_ALIGNED
     88 } inrange_t;
     89 
     90 /* input filenames and record numbers, for diagnostics only */
     91 #define	STDIN_NAME	"<stdin>"
     92 static char	*cur_input_name;
     93 static off_t	recin;
     94 
     95 static FILE	*Wtmpx, *Temp;
     96 
     97 struct	dtab
     98 {
     99 	off_t	d_off1;		/* file offset start */
    100 	off_t	d_off2;		/* file offset stop */
    101 	time_t	d_adj;		/* time adjustment */
    102 	struct dtab *d_ndp;	/* next record */
    103 };
    104 
    105 static struct	dtab	*Fdp;	/* list header */
    106 static struct	dtab	*Ldp;	/* list trailer */
    107 
    108 static time_t 	lastmonth, nextmonth;
    109 
    110 static struct	futmpx	Ut, Ut2;
    111 
    112 static int winp(FILE *, struct futmpx *);
    113 static void mkdtab(off_t);
    114 static void setdtab(off_t, struct futmpx *, struct futmpx *);
    115 static void adjust(off_t, struct futmpx *);
    116 static int invalid(char *);
    117 static void scanfile(void);
    118 static inrange_t inrange(void);
    119 static void wcomplain(char *);
    120 
    121 int
    122 main(int argc, char **argv)
    123 {
    124 	time_t tloc;
    125 	struct tm *tmp;
    126 	int year;
    127 	int month;
    128 	off_t rectmpin;
    129 
    130 	(void) setlocale(LC_ALL, "");
    131 	setbuf(stdout, NULL);
    132 
    133 	(void) time(&tloc);
    134 	tmp = localtime(&tloc);
    135 	year = tmp->tm_year;
    136 	month = tmp->tm_mon + 1;
    137 	lastmonth = ((year + 1900 - 1970) * 365 +
    138 	    (month - 1) * 30) * DAYEPOCH;
    139 	nextmonth = ((year + 1900 - 1970) * 365 +
    140 	    (month + 1) * 30) * DAYEPOCH;
    141 
    142 	if (argc < 2) {
    143 		argv[argc] = "-";
    144 		argc++;
    145 	}
    146 
    147 	/*
    148 	 * Almost all system call failures in this program are unrecoverable
    149 	 * and therefore fatal.  Typical causes might be lack of memory or
    150 	 * of space in a filesystem.  If necessary, the system administrator
    151 	 * can invoke /usr/lib/acct/runacct interactively after making room
    152 	 * to complete the remaining phases of last night's accounting.
    153 	 */
    154 	if ((Temp = tmpfile()) == NULL) {
    155 		perror("Cannot create temporary file");
    156 		return (EXIT_FAILURE);
    157 	}
    158 
    159 	while (--argc > 0) {
    160 		argv++;
    161 		if (strcmp(*argv, "-") == 0) {
    162 			Wtmpx = stdin;
    163 			cur_input_name = STDIN_NAME;
    164 		} else if ((Wtmpx = fopen(*argv, "r")) == NULL) {
    165 			(void) fprintf(stderr, "Cannot open %s: %s\n",
    166 			    *argv, strerror(errno));
    167 			return (EXIT_FAILURE);
    168 		} else {
    169 			cur_input_name = *argv;
    170 		}
    171 		/*
    172 		 * Filter records reading from current input stream Wtmpx,
    173 		 * writing to Temp.
    174 		 */
    175 		scanfile();
    176 
    177 		if (Wtmpx != stdin)
    178 			(void) fclose(Wtmpx);
    179 	}
    180 	/* flush and rewind Temp for readback */
    181 	if (fflush(Temp) != 0) {
    182 		perror("<temporary file>: fflush");
    183 		return (EXIT_FAILURE);
    184 	}
    185 	if (fseeko(Temp, (off_t)0L, SEEK_SET) != 0) {
    186 		perror("<temporary file>: seek");
    187 		return (EXIT_FAILURE);
    188 	}
    189 	/* second pass: apply time adjustments */
    190 	rectmpin = 0;
    191 	while (winp(Temp, &Ut)) {
    192 		adjust(rectmpin, &Ut);
    193 		rectmpin += UTRSZ;
    194 		if (fwrite(&Ut, UTRSZ, 1, stdout) < 1) {
    195 			perror("<stdout>: fwrite");
    196 			return (EXIT_FAILURE);
    197 		}
    198 	}
    199 	(void) fclose(Temp);
    200 	/*
    201 	 * Detect if we've run out of space (say) and exit unsuccessfully
    202 	 * so that downstream accounting utilities won't start processing an
    203 	 * incomplete tmpwtmp file.
    204 	 */
    205 	if (fflush(stdout) != 0) {
    206 		perror("<stdout>: fflush");
    207 		return (EXIT_FAILURE);
    208 	}
    209 	return (EXIT_SUCCESS);
    210 }
    211 
    212 static int
    213 winp(FILE *f, struct futmpx *w)
    214 {
    215 	if (fread(w, (size_t)UTRSZ, (size_t)1, f) != 1)
    216 		return (0);
    217 	if ((w->ut_type >= EMPTY) && (w->ut_type <= UTMAXTYPE))
    218 		return (1);
    219 	else {
    220 		(void) fprintf(stderr, "Bad temp file at offset %lld\n",
    221 		    (longlong_t)(ftell(f) - UTRSZ));
    222 		/*
    223 		 * If input was corrupt, neither ut_line nor ut_user can be
    224 		 * relied on to be \0-terminated.  Even fixing the precision
    225 		 * does not entirely guard against this.
    226 		 */
    227 		(void) fprintf(stderr,
    228 		    "ut_line \"%-12.12s\" ut_user \"%-8.8s\" ut_xtime %ld\n",
    229 		    w->ut_line, w->ut_user, (long)w->ut_xtime);
    230 		exit(EXIT_FAILURE);
    231 	}
    232 	/* NOTREACHED */
    233 }
    234 
    235 static void
    236 mkdtab(off_t p)
    237 {
    238 
    239 	struct dtab *dp;
    240 
    241 	dp = Ldp;
    242 	if (dp == NULL) {
    243 		dp = calloc(sizeof (struct dtab), 1);
    244 		if (dp == NULL) {
    245 			(void) fprintf(stderr, "out of memory\n");
    246 			exit(EXIT_FAILURE);
    247 		}
    248 		Fdp = Ldp = dp;
    249 	}
    250 	dp->d_off1 = p;
    251 }
    252 
    253 static void
    254 setdtab(off_t p, struct futmpx *w1, struct futmpx *w2)
    255 {
    256 	struct dtab *dp;
    257 
    258 	if ((dp = Ldp) == NULL) {
    259 		(void) fprintf(stderr, "no dtab\n");
    260 		exit(EXIT_FAILURE);
    261 	}
    262 	dp->d_off2 = p;
    263 	dp->d_adj = w2->ut_xtime - w1->ut_xtime;
    264 	if ((Ldp = calloc(sizeof (struct dtab), 1)) == NULL) {
    265 		(void) fprintf(stderr, "out of memory\n");
    266 		exit(EXIT_FAILURE);
    267 	}
    268 	Ldp->d_off1 = dp->d_off1;
    269 	dp->d_ndp = Ldp;
    270 }
    271 
    272 static void
    273 adjust(off_t p, struct futmpx *w)
    274 {
    275 
    276 	off_t pp;
    277 	struct dtab *dp;
    278 
    279 	pp = p;
    280 
    281 	for (dp = Fdp; dp != NULL; dp = dp->d_ndp) {
    282 		if (dp->d_adj == 0)
    283 			continue;
    284 		if (pp >= dp->d_off1 && pp <= dp->d_off2)
    285 			w->ut_xtime += dp->d_adj;
    286 	}
    287 }
    288 
    289 /*
    290  * invalid() determines whether the name field adheres to the criteria
    291  * set forth in acctcon1.  If returns VALID if the name is ok, or
    292  * INVALID if the name violates conventions.
    293  */
    294 
    295 static int
    296 invalid(char *name)
    297 {
    298 	int	i;
    299 
    300 	for (i = 0; i < NSZ; i++) {
    301 		if (name[i] == '\0')
    302 			return (VALID);
    303 		if (! (isalnum(name[i]) || (name[i] == '$') ||
    304 		    (name[i] == ' ') || (name[i] == '.') ||
    305 		    (name[i] == '_') || (name[i] == '-'))) {
    306 			return (INVALID);
    307 		}
    308 	}
    309 	return (VALID);
    310 }
    311 
    312 /*
    313  * scanfile:
    314  * 1)  	reads the current input file
    315  * 2)   filters for process records in time range of interest and for
    316  *      other types of records deemed interesting to acctcon downstream
    317  * 3)   picks up time changes with setdtab() if in multiuser mode, which
    318  *      will be applied when the temp file is read back
    319  * 4)   changes bad login names to INVALID
    320  * 5)   recovers from common cases of wtmpx corruption (loss of record
    321  *      alignment).
    322  * All of the static globals are used directly or indirectly.
    323  *
    324  * When wtmpfix is asked to process several input files in succession,
    325  * some state needs to be preserved from one scanfile() invocation to the
    326  * next.  Aside from the temp file position, we remember whether we were
    327  * in multi-user mode or not.  Absent evidence to the contrary, we begin
    328  * processing assuming multi-user mode, because runacct's wtmpx rotation
    329  * normally gives us a file recently initialized by utmp2wtmp(1M) with no
    330  * older RUN_LVL records surviving.
    331  */
    332 
    333 static void
    334 scanfile()
    335 {
    336 	struct stat Wtstat;
    337 	off_t residue = 0;	/* input file size mod UTRSZ */
    338 	/*
    339 	 * lastok will be the offset of the beginning of the most recent
    340 	 * manifestly plausible and interesting input record in the current
    341 	 * input file, if any.
    342 	 * An invariant at loop entry is -UTRSZ <= lastok <= recin - UTRSZ.
    343 	 */
    344 	off_t lastok = -(off_t)UTRSZ;
    345 	static off_t rectmp;	/* current temp file position */
    346 	static boolean_t multimode = B_TRUE; /* multi-user RUN_LVL in force */
    347 	inrange_t is_ok;	/* caches inrange() result */
    348 	/*
    349 	 * During normal operation, records are of interest and copied to
    350 	 * the output when is_ok >= INRANGE_PASS, ignored and dropped when
    351 	 * is_ok == INRANGE_DROP, and evidence of corruption otherwise.
    352 	 * While we are trying to recover from a corruption and hunting for
    353 	 * records with sufficient redundancy to confirm that we have reached
    354 	 * proper alignment again, we'll want is_ok >= INRANGE_ALIGNED.
    355 	 * The value of want_ok is the minimum inrange() result of current
    356 	 * interest.  It is raised to INRANGE_ALIGNED during ongoing recovery
    357 	 * and dropped back to INRANGE_PASS when we have recovered alignment.
    358 	 */
    359 	inrange_t want_ok = INRANGE_PASS;
    360 	boolean_t recovered = B_FALSE; /* true after a successful recovery */
    361 	int n;
    362 
    363 	if (fstat(fileno(Wtmpx), &Wtstat) == -1) {
    364 		(void) fprintf(stderr,
    365 		    "Cannot stat %s (will read sequentially): %s\n",
    366 		    cur_input_name, strerror(errno));
    367 	} else if ((Wtstat.st_mode & S_IFMT) == S_IFREG) {
    368 		residue = Wtstat.st_size % UTRSZ;
    369 	}
    370 
    371 	/* if residue != 0, part of the file may be misaligned */
    372 	for (recin = 0;
    373 	    ((n = fread(&Ut, (size_t)UTRSZ, (size_t)1, Wtmpx)) > 0) ||
    374 	    (residue > 0);
    375 	    recin += UTRSZ) {
    376 		if (n == 0) {
    377 			/*
    378 			 * Implying residue > 0 and want_ok == INRANGE_PASS.
    379 			 * It isn't worth telling an I/O error from EOF here.
    380 			 * But one case is worth catching to avoid issuing a
    381 			 * confusing message below.  When the previous record
    382 			 * had been ok, we just drop the current truncated
    383 			 * record and bail out of the loop -- no seeking back.
    384 			 */
    385 			if (lastok == recin - UTRSZ) {
    386 				wcomplain("file ends in mid-record, "
    387 				    "final partial record dropped");
    388 				break;
    389 			} else {
    390 				wcomplain("file ends in mid-record");
    391 				/* handled below like a corrupted record */
    392 				is_ok = INRANGE_ERR;
    393 			}
    394 		} else
    395 			is_ok = inrange();
    396 
    397 		/* alignment recovery logic */
    398 		if ((residue > 0) && (is_ok == INRANGE_ERR)) {
    399 			/*
    400 			 * "Let's go back to the last place where we knew
    401 			 * where we were..."
    402 			 * In fact, if the last record had been fine and we
    403 			 * know there's at least one whole record ahead, we
    404 			 * might move forward here  (by residue bytes, less
    405 			 * than one record's worth).  In any case, we align
    406 			 * ourselves to an integral number of records before
    407 			 * the end of the file.
    408 			 */
    409 			wcomplain("suspecting misaligned records, "
    410 			    "repositioning");
    411 			recin = lastok + UTRSZ + residue;
    412 			residue = 0;
    413 			if (fseeko(Wtmpx, recin, SEEK_SET) != 0) {
    414 				(void) fprintf(stderr, "%s: seek: %s\n",
    415 				    cur_input_name, strerror(errno));
    416 				exit(EXIT_FAILURE);
    417 			}
    418 			wcomplain("starting re-scan");
    419 			/*
    420 			 * While want_ok is elevated, only unequivocal records
    421 			 * with inrange() == INRANGE_ALIGNED will be admitted
    422 			 * to latch onto the tentative new alignment.
    423 			 */
    424 			want_ok = INRANGE_ALIGNED;
    425 			/*
    426 			 * Compensate for the loop continuation.  Doing
    427 			 * it this way gets the correct offset reported
    428 			 * in the re-scan message above.
    429 			 */
    430 			recin -= UTRSZ;
    431 			continue;
    432 		}
    433 		/* assert: residue == 0 or is_ok >= INRANGE_DROP here */
    434 		if (is_ok < want_ok)
    435 			/* record of no further interest */
    436 			continue;
    437 		if (want_ok == INRANGE_ALIGNED) {
    438 			wcomplain("now recognizing aligned records again");
    439 			want_ok = INRANGE_PASS;
    440 			recovered = B_TRUE;
    441 		}
    442 		/*
    443 		 * lastok must track recin whenever the current record is
    444 		 * being processed and written out to our temp file, to avoid
    445 		 * reprocessing any bits already done when we readjust our
    446 		 * alignment.
    447 		 */
    448 		lastok = recin;
    449 
    450 		/* now we have a good wtmpx record, do more processing */
    451 
    452 		if (rectmp == 0 || Ut.ut_type == BOOT_TIME)
    453 			mkdtab(rectmp);
    454 		if (Ut.ut_type == RUN_LVL) {
    455 			/* inrange() already checked the "run-level " part */
    456 			if (Ut.ut_line[RLVLMSG_LEN] == 'S')
    457 				multimode = B_FALSE;
    458 			else if ((Ut.ut_line[RLVLMSG_LEN] == '2') ||
    459 			    (Ut.ut_line[RLVLMSG_LEN] == '3') ||
    460 			    (Ut.ut_line[RLVLMSG_LEN] == '4'))
    461 				multimode = B_TRUE;
    462 		}
    463 		if (invalid(Ut.ut_name) == INVALID) {
    464 			(void) fprintf(stderr,
    465 			    "wtmpfix: logname \"%*.*s\" changed "
    466 			    "to \"INVALID\"\n", OUTPUT_NSZ,
    467 			    OUTPUT_NSZ, Ut.ut_name);
    468 			(void) strncpy(Ut.ut_name, "INVALID", NSZ);
    469 		}
    470 		/*
    471 		 * Special case: OLD_TIME should be immediately followed by
    472 		 * NEW_TIME.
    473 		 * We make no attempt at alignment recovery between these
    474 		 * two: if there's junk at this point in the input, then
    475 		 * a NEW_TIME seen after the junk probably won't be the one
    476 		 * we are looking for.
    477 		 */
    478 		if (Ut.ut_type == OLD_TIME) {
    479 			/*
    480 			 * Make recin refer to the expected NEW_TIME.
    481 			 * Loop continuation will increment it again
    482 			 * for the record we're about to read now.
    483 			 */
    484 			recin += UTRSZ;
    485 			if (!fread(&Ut2, (size_t)UTRSZ, (size_t)1, Wtmpx)) {
    486 				wcomplain("input truncated after OLD_TIME - "
    487 				    "giving up");
    488 				exit(EXIT_FAILURE);
    489 			}
    490 			/*
    491 			 * Rudimentary NEW_TIME sanity check.  Not as thorough
    492 			 * as in inrange(), but then we have redundancy from
    493 			 * context here, since we're just after a plausible
    494 			 * OLD_TIME record.
    495 			 */
    496 			if ((Ut2.ut_type != NEW_TIME) ||
    497 			    (strcmp(Ut2.ut_line, NTIME_MSG) != 0)) {
    498 				wcomplain("NEW_TIME expected but missing "
    499 				    "after OLD_TIME - giving up");
    500 				exit(EXIT_FAILURE);
    501 			}
    502 			lastok = recin;
    503 			if (multimode == B_TRUE)
    504 				setdtab(rectmp, &Ut, &Ut2);
    505 			rectmp += 2 * UTRSZ;
    506 			if ((fwrite(&Ut, UTRSZ, 1, Temp) < 1) ||
    507 			    (fwrite(&Ut2, UTRSZ, 1, Temp) < 1)) {
    508 				perror("<temporary file>: fwrite");
    509 				exit(EXIT_FAILURE);
    510 			}
    511 			continue;
    512 		}
    513 		if (fwrite(&Ut, UTRSZ, 1, Temp) < 1) {
    514 			perror("<temporary file>: fwrite");
    515 			exit(EXIT_FAILURE);
    516 		}
    517 		rectmp += UTRSZ;
    518 	}
    519 	if (want_ok == INRANGE_ALIGNED) {
    520 		wcomplain("EOF reached without recognizing another aligned "
    521 		    "record with certainty. This file may need to be "
    522 		    "repaired by hand.\n");
    523 	} else if (recovered == B_TRUE) {
    524 		/*
    525 		 * There may have been a number of wcomplain() messages
    526 		 * since we reported about the re-scan, so it bears repeating
    527 		 * at the end that not all was well.
    528 		 */
    529 		wcomplain("EOF reached after recovering from corruption "
    530 		    "in the middle of the file.  This file may need to be "
    531 		    "repaired by hand.\n");
    532 	}
    533 }
    534 
    535 /*
    536  * inrange: inspect what we hope to be one wtmpx record.
    537  * Globals:  Ut, lastmonth, nextmonth;  recin, cur_input_name (diagnostics)
    538  * Return values:
    539  * INRANGE_ERR     -- an inconsistency was detected, input file corrupted
    540  * INRANGE_DROP    -- Ut appears consistent but isn't of interest
    541  *                    (of process type and outside the time range we want)
    542  * INRANGE_PASS    -- Ut appears consistent and this record is of interest
    543  * INRANGE_ALIGNED -- same, and it is also redundant enough to be sure
    544  *                    that we're correctly aligned on record boundaries
    545  */
    546 #define	UNEXPECTED_UT_PID \
    547 	(Ut.ut_pid != 0) || \
    548 	(Ut.ut_exit.e_termination != 0) || \
    549 	(Ut.ut_exit.e_exit != 0)
    550 
    551 static inrange_t
    552 inrange()
    553 {
    554 	/* pid_t is signed so that fork() can return -1.  Exploit this. */
    555 	if (Ut.ut_pid < 0) {
    556 		wcomplain("negative pid");
    557 		return (INRANGE_ERR);
    558 	}
    559 
    560 	/* the legal values for ut_type are enumerated in <utmp.h> */
    561 	switch (Ut.ut_type) {
    562 	case EMPTY:
    563 		if (UNEXPECTED_UT_PID) {
    564 			wcomplain("nonzero pid or status in EMPTY record");
    565 			return (INRANGE_ERR);
    566 		}
    567 		/*
    568 		 * We'd like to have Ut.ut_user[0] == '\0' here, but sadly
    569 		 * this isn't always so, so we can't rely on it.
    570 		 */
    571 		return (INRANGE_DROP);
    572 	case RUN_LVL:
    573 		/* ut_line must have come from the RUNLVL_MSG pattern */
    574 		if (strncmp(Ut.ut_line, RUN_LEVEL_MSG, RLVLMSG_LEN) != 0) {
    575 			wcomplain("RUN_LVL record doesn't say `"
    576 			    RUN_LEVEL_MSG "'");
    577 			return (INRANGE_ERR);
    578 		}
    579 		/*
    580 		 * The ut_pid, termination, and exit status fields have
    581 		 * special meaning in this case, and none of them is
    582 		 * suitable for checking.  And we won't insist on ut_user
    583 		 * to always be an empty string.
    584 		 */
    585 		return (INRANGE_ALIGNED);
    586 	case BOOT_TIME:
    587 		if (UNEXPECTED_UT_PID) {
    588 			wcomplain("nonzero pid or status in BOOT_TIME record");
    589 			return (INRANGE_ERR);
    590 		}
    591 		if (strcmp(Ut.ut_line, BOOT_MSG) != 0) {
    592 			wcomplain("BOOT_TIME record doesn't say `"
    593 			    BOOT_MSG "'");
    594 			return (INRANGE_ERR);
    595 		}
    596 		return (INRANGE_ALIGNED);
    597 	case OLD_TIME:
    598 		if (UNEXPECTED_UT_PID) {
    599 			wcomplain("nonzero pid or status in OLD_TIME record");
    600 			return (INRANGE_ERR);
    601 		}
    602 		if (strcmp(Ut.ut_line, OTIME_MSG) != 0) {
    603 			wcomplain("OLD_TIME record doesn't say `"
    604 			    OTIME_MSG "'");
    605 			return (INRANGE_ERR);
    606 		}
    607 		return (INRANGE_ALIGNED);
    608 	case NEW_TIME:
    609 		/*
    610 		 * We don't actually expect to see any here.  If they follow
    611 		 * an OLD_TIME record as they should, they'll be handled on
    612 		 * the fly in scanfile().  But we might still run into one
    613 		 * if the input is somehow corrupted.
    614 		 */
    615 		if (UNEXPECTED_UT_PID) {
    616 			wcomplain("nonzero pid or status in NEW_TIME record");
    617 			return (INRANGE_ERR);
    618 		}
    619 		if (strcmp(Ut.ut_line, NTIME_MSG) != 0) {
    620 			wcomplain("NEW_TIME record doesn't say `"
    621 			    NTIME_MSG "'");
    622 			return (INRANGE_ERR);
    623 		}
    624 		return (INRANGE_ALIGNED);
    625 
    626 	/* the four *_PROCESS ut_types have a lot in common */
    627 	case USER_PROCESS:
    628 		/*
    629 		 * Catch two special cases first: psradm records have no id
    630 		 * and no pid, while root login over FTP may not have a
    631 		 * valid ut_user and may have garbage in ut_id[3].
    632 		 */
    633 		if ((strcmp(Ut.ut_user, "psradm") == 0) &&
    634 		    (Ut.ut_id[0] == '\0') &&
    635 		    (Ut.ut_pid > 0)) {
    636 			if ((Ut.ut_xtime > lastmonth) &&
    637 			    (Ut.ut_xtime < nextmonth)) {
    638 				return (INRANGE_ALIGNED);
    639 			} else {
    640 				return (INRANGE_DROP);
    641 			}
    642 		}
    643 		if ((Ut.ut_user[0] == '\0') &&
    644 		    (strncmp(Ut.ut_id, "ftp", 3) == 0) &&
    645 		    (strncmp(Ut.ut_line, "ftp", 3) == 0)) {
    646 			if ((Ut.ut_xtime > lastmonth) &&
    647 			    (Ut.ut_xtime < nextmonth)) {
    648 				return (INRANGE_ALIGNED);
    649 			} else {
    650 				return (INRANGE_DROP);
    651 			}
    652 		}
    653 		/* FALLTHROUGH */
    654 	case LOGIN_PROCESS:
    655 		if (Ut.ut_user[0] == '\0') {
    656 			wcomplain("missing username in process record");
    657 			return (INRANGE_ERR);
    658 		}
    659 		/* FALLTHROUGH */
    660 	case INIT_PROCESS:
    661 		/*
    662 		 * INIT_PROCESS and DEAD_PROCESS records can come with an
    663 		 * empty ut_user in degenerate cases (e.g. syntax errors
    664 		 * like a comment-only process field in /etc/inittab).
    665 		 * But in an INIT_PROCESS, LOGIN_PROCESS, or USER_PROCESS
    666 		 * record, we expect a respectable ut_pid.
    667 		 */
    668 		if (Ut.ut_pid == 0) {
    669 			wcomplain("null pid in process record");
    670 			return (INRANGE_ERR);
    671 		}
    672 		/* FALLTHROUGH */
    673 	case DEAD_PROCESS:
    674 		/*
    675 		 * DEAD_PROCESS records with a null ut_pid can be produced
    676 		 * by gnome-terminal (normally seen in utmpx only, but they
    677 		 * can leak into wtmpx in rare circumstances).
    678 		 * Unfortunately, ut_id can't be relied on to contain
    679 		 * anything in particular.  (E.g., sshd might leave it
    680 		 * 0-initialized.)  This leaves almost no verifiable
    681 		 * redundancy here beyond the ut_type.
    682 		 * At least we insist on a reasonable timestamp.
    683 		 */
    684 		if (Ut.ut_xtime <= 0) {
    685 			wcomplain("non-positive time in process record");
    686 			return (INRANGE_ERR);
    687 		}
    688 		if ((Ut.ut_xtime > lastmonth) &&
    689 		    (Ut.ut_xtime < nextmonth)) {
    690 			return (INRANGE_PASS);
    691 		} else {
    692 			return (INRANGE_DROP);
    693 		}
    694 	case ACCOUNTING:
    695 		/*
    696 		 * If we recognize one of the three reason strings passed
    697 		 * by the /usr/lib/acct shell scripts to acctwtmp, we
    698 		 * exploit the available redundancy they offer.  But
    699 		 * acctwtmp could have been invoked by custom scripts or
    700 		 * interactively with other reason strings in the first
    701 		 * argument, so anything we don't recognize does not
    702 		 * constitute evidence for corruption.
    703 		 */
    704 		if ((strcmp(Ut.ut_line, RUNACCT_MSG) != 0) &&
    705 		    (strcmp(Ut.ut_line, ACCTG_ON_MSG) != 0) &&
    706 		    (strcmp(Ut.ut_line, ACCTG_OFF_MSG) != 0)) {
    707 			return (INRANGE_DROP);
    708 		}
    709 		return (INRANGE_ALIGNED);
    710 	case DOWN_TIME:
    711 		if (UNEXPECTED_UT_PID) {
    712 			wcomplain("nonzero pid or status in DOWN_TIME record");
    713 			return (INRANGE_ERR);
    714 		}
    715 		if (strcmp(Ut.ut_line, DOWN_MSG) != 0) {
    716 			wcomplain("DOWN_TIME record doesn't say `"
    717 			    DOWN_MSG "'");
    718 			return (INRANGE_ERR);
    719 		}
    720 		return (INRANGE_ALIGNED);
    721 	default:
    722 		wcomplain("ut_type out of range");
    723 		return (INRANGE_ERR);
    724 	}
    725 	/* NOTREACHED */
    726 }
    727 
    728 static void
    729 wcomplain(char *msg)
    730 {
    731 	(void) fprintf(stderr, "%s: offset %lld: %s\n", cur_input_name,
    732 	    (longlong_t)recin, msg);
    733 }
    734