xref: /onnv-gate/usr/src/cmd/acct/wtmpfix.c (revision 7003:9a4cb993aea7)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5*7003Srm88369  * Common Development and Distribution License (the "License").
6*7003Srm88369  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
210Sstevel@tonic-gate 
220Sstevel@tonic-gate /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
230Sstevel@tonic-gate /*	  All Rights Reserved  	*/
240Sstevel@tonic-gate 
25428Ssl108498 /*
26*7003Srm88369  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
27428Ssl108498  * Use is subject to license terms.
28428Ssl108498  */
290Sstevel@tonic-gate 
300Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
31*7003Srm88369 
320Sstevel@tonic-gate /*
330Sstevel@tonic-gate  * wtmpfix - adjust wtmpx file and remove date changes.
340Sstevel@tonic-gate  *	wtmpfix <wtmpx1 >wtmpx2
350Sstevel@tonic-gate  *
36*7003Srm88369  *	Can recover to some extent from wtmpx corruption.
370Sstevel@tonic-gate  */
380Sstevel@tonic-gate 
390Sstevel@tonic-gate #include <stdio.h>
400Sstevel@tonic-gate #include <sys/types.h>
41*7003Srm88369 #include <sys/stat.h>
420Sstevel@tonic-gate #include <sys/param.h>
430Sstevel@tonic-gate #include "acctdef.h"
440Sstevel@tonic-gate #include <utmpx.h>
450Sstevel@tonic-gate #include <time.h>
460Sstevel@tonic-gate #include <ctype.h>
470Sstevel@tonic-gate #include <locale.h>
480Sstevel@tonic-gate #include <stdlib.h>
49*7003Srm88369 #include <string.h>
50*7003Srm88369 #include <errno.h>
510Sstevel@tonic-gate 
520Sstevel@tonic-gate #define	DAYEPOCH	(60 * 60 * 24)
53*7003Srm88369 #define	UTRSZ		(sizeof (struct futmpx)) /* file record size */
54*7003Srm88369 
55*7003Srm88369 /*
56*7003Srm88369  * The acctsh(1M) shell scripts startup(1M) and shutacct(1M) as well as the
57*7003Srm88369  * runacct script each pass their own specific reason strings in the first
58*7003Srm88369  * argument to acctwtmp(1M), to be propagated into ut_line fields.  Additional
59*7003Srm88369  * reasons (RUNLVL_MSG, ..., DOWN_MSG), used by compiled code, are defined in
60*7003Srm88369  * <utmp.h> as preprocessor constants.
61*7003Srm88369  * For simplicity we predefine similar constants for the scripted strings
62*7003Srm88369  * here, as no other compiled code uses those.
63*7003Srm88369  * Moreover, we need a variant of RUNLVL_MSG without the "%c" at the end.
64*7003Srm88369  * We shall use the fact that ut_line[RLVLMSG_LEN] will extract the char
65*7003Srm88369  * in the %c position ('S', '2', ...).
66*7003Srm88369  * Since all of these string constants are '\0' terminated, they can safely
67*7003Srm88369  * be used with strcmp() even when ut_line is not.
68*7003Srm88369  */
69*7003Srm88369 #define	RUN_LEVEL_MSG	"run-level "
70*7003Srm88369 #define	ACCTG_ON_MSG	"acctg on"
71*7003Srm88369 #define	ACCTG_OFF_MSG	"acctg off"
72*7003Srm88369 #define	RUNACCT_MSG	"runacct"
730Sstevel@tonic-gate 
74*7003Srm88369 #define	RLVLMSG_LEN	(sizeof (RUN_LEVEL_MSG) - 1)
75*7003Srm88369 
76*7003Srm88369 /*
77*7003Srm88369  * Records encountered are classified as one of the following:  corrupted;
78*7003Srm88369  * ok but devoid of interest to acctcon downstream;  ok and interesting;
79*7003Srm88369  * or ok and even redundant enough to latch onto a new alignment whilst
80*7003Srm88369  * recovering from a corruption.
81*7003Srm88369  * The ordering among these four symbolic values is significant.
82*7003Srm88369  */
83*7003Srm88369 typedef enum {
84*7003Srm88369 	INRANGE_ERR = -1,
85*7003Srm88369 	INRANGE_DROP,
86*7003Srm88369 	INRANGE_PASS,
87*7003Srm88369 	INRANGE_ALIGNED
88*7003Srm88369 } inrange_t;
89*7003Srm88369 
90*7003Srm88369 /* input filenames and record numbers, for diagnostics only */
91*7003Srm88369 #define	STDIN_NAME	"<stdin>"
92*7003Srm88369 static char	*cur_input_name;
93*7003Srm88369 static off_t	recin;
94*7003Srm88369 
95*7003Srm88369 static FILE	*Wtmpx, *Temp;
960Sstevel@tonic-gate 
970Sstevel@tonic-gate struct	dtab
980Sstevel@tonic-gate {
990Sstevel@tonic-gate 	off_t	d_off1;		/* file offset start */
1000Sstevel@tonic-gate 	off_t	d_off2;		/* file offset stop */
1010Sstevel@tonic-gate 	time_t	d_adj;		/* time adjustment */
1020Sstevel@tonic-gate 	struct dtab *d_ndp;	/* next record */
1030Sstevel@tonic-gate };
1040Sstevel@tonic-gate 
105*7003Srm88369 static struct	dtab	*Fdp;	/* list header */
106*7003Srm88369 static struct	dtab	*Ldp;	/* list trailer */
1070Sstevel@tonic-gate 
108*7003Srm88369 static time_t 	lastmonth, nextmonth;
1090Sstevel@tonic-gate 
110*7003Srm88369 static struct	futmpx	Ut, Ut2;
1110Sstevel@tonic-gate 
112*7003Srm88369 static int winp(FILE *, struct futmpx *);
1130Sstevel@tonic-gate static void mkdtab(off_t);
114*7003Srm88369 static void setdtab(off_t, struct futmpx *, struct futmpx *);
115*7003Srm88369 static void adjust(off_t, struct futmpx *);
1160Sstevel@tonic-gate static int invalid(char *);
1170Sstevel@tonic-gate static void scanfile(void);
118*7003Srm88369 static inrange_t inrange(void);
119*7003Srm88369 static void wcomplain(char *);
1200Sstevel@tonic-gate 
1210Sstevel@tonic-gate int
main(int argc,char ** argv)1220Sstevel@tonic-gate main(int argc, char **argv)
1230Sstevel@tonic-gate {
1240Sstevel@tonic-gate 	time_t tloc;
1250Sstevel@tonic-gate 	struct tm *tmp;
126*7003Srm88369 	int year;
127*7003Srm88369 	int month;
128*7003Srm88369 	off_t rectmpin;
1290Sstevel@tonic-gate 
1300Sstevel@tonic-gate 	(void) setlocale(LC_ALL, "");
1310Sstevel@tonic-gate 	setbuf(stdout, NULL);
1320Sstevel@tonic-gate 
133*7003Srm88369 	(void) time(&tloc);
1340Sstevel@tonic-gate 	tmp = localtime(&tloc);
1350Sstevel@tonic-gate 	year = tmp->tm_year;
1360Sstevel@tonic-gate 	month = tmp->tm_mon + 1;
1370Sstevel@tonic-gate 	lastmonth = ((year + 1900 - 1970) * 365 +
1380Sstevel@tonic-gate 	    (month - 1) * 30) * DAYEPOCH;
1390Sstevel@tonic-gate 	nextmonth = ((year + 1900 - 1970) * 365 +
1400Sstevel@tonic-gate 	    (month + 1) * 30) * DAYEPOCH;
1410Sstevel@tonic-gate 
1420Sstevel@tonic-gate 	if (argc < 2) {
1430Sstevel@tonic-gate 		argv[argc] = "-";
1440Sstevel@tonic-gate 		argc++;
1450Sstevel@tonic-gate 	}
1460Sstevel@tonic-gate 
147*7003Srm88369 	/*
148*7003Srm88369 	 * Almost all system call failures in this program are unrecoverable
149*7003Srm88369 	 * and therefore fatal.  Typical causes might be lack of memory or
150*7003Srm88369 	 * of space in a filesystem.  If necessary, the system administrator
151*7003Srm88369 	 * can invoke /usr/lib/acct/runacct interactively after making room
152*7003Srm88369 	 * to complete the remaining phases of last night's accounting.
153*7003Srm88369 	 */
154*7003Srm88369 	if ((Temp = tmpfile()) == NULL) {
155*7003Srm88369 		perror("Cannot create temporary file");
156*7003Srm88369 		return (EXIT_FAILURE);
1570Sstevel@tonic-gate 	}
1580Sstevel@tonic-gate 
1590Sstevel@tonic-gate 	while (--argc > 0) {
1600Sstevel@tonic-gate 		argv++;
161*7003Srm88369 		if (strcmp(*argv, "-") == 0) {
1620Sstevel@tonic-gate 			Wtmpx = stdin;
163*7003Srm88369 			cur_input_name = STDIN_NAME;
164*7003Srm88369 		} else if ((Wtmpx = fopen(*argv, "r")) == NULL) {
165*7003Srm88369 			(void) fprintf(stderr, "Cannot open %s: %s\n",
166*7003Srm88369 			    *argv, strerror(errno));
167*7003Srm88369 			return (EXIT_FAILURE);
168*7003Srm88369 		} else {
169*7003Srm88369 			cur_input_name = *argv;
1700Sstevel@tonic-gate 		}
171*7003Srm88369 		/*
172*7003Srm88369 		 * Filter records reading from current input stream Wtmpx,
173*7003Srm88369 		 * writing to Temp.
174*7003Srm88369 		 */
1750Sstevel@tonic-gate 		scanfile();
1760Sstevel@tonic-gate 
1770Sstevel@tonic-gate 		if (Wtmpx != stdin)
178*7003Srm88369 			(void) fclose(Wtmpx);
1790Sstevel@tonic-gate 	}
180*7003Srm88369 	/* flush and rewind Temp for readback */
181*7003Srm88369 	if (fflush(Temp) != 0) {
182*7003Srm88369 		perror("<temporary file>: fflush");
183*7003Srm88369 		return (EXIT_FAILURE);
184*7003Srm88369 	}
185*7003Srm88369 	if (fseeko(Temp, (off_t)0L, SEEK_SET) != 0) {
186*7003Srm88369 		perror("<temporary file>: seek");
187*7003Srm88369 		return (EXIT_FAILURE);
1880Sstevel@tonic-gate 	}
189*7003Srm88369 	/* second pass: apply time adjustments */
190*7003Srm88369 	rectmpin = 0;
191*7003Srm88369 	while (winp(Temp, &Ut)) {
192*7003Srm88369 		adjust(rectmpin, &Ut);
193*7003Srm88369 		rectmpin += UTRSZ;
194*7003Srm88369 		if (fwrite(&Ut, UTRSZ, 1, stdout) < 1) {
195*7003Srm88369 			perror("<stdout>: fwrite");
196*7003Srm88369 			return (EXIT_FAILURE);
197*7003Srm88369 		}
1980Sstevel@tonic-gate 	}
199*7003Srm88369 	(void) fclose(Temp);
200*7003Srm88369 	/*
201*7003Srm88369 	 * Detect if we've run out of space (say) and exit unsuccessfully
202*7003Srm88369 	 * so that downstream accounting utilities won't start processing an
203*7003Srm88369 	 * incomplete tmpwtmp file.
204*7003Srm88369 	 */
205*7003Srm88369 	if (fflush(stdout) != 0) {
206*7003Srm88369 		perror("<stdout>: fflush");
207*7003Srm88369 		return (EXIT_FAILURE);
208*7003Srm88369 	}
209*7003Srm88369 	return (EXIT_SUCCESS);
2100Sstevel@tonic-gate }
2110Sstevel@tonic-gate 
2120Sstevel@tonic-gate static int
winp(FILE * f,struct futmpx * w)213*7003Srm88369 winp(FILE *f, struct futmpx *w)
2140Sstevel@tonic-gate {
215*7003Srm88369 	if (fread(w, (size_t)UTRSZ, (size_t)1, f) != 1)
2160Sstevel@tonic-gate 		return (0);
2170Sstevel@tonic-gate 	if ((w->ut_type >= EMPTY) && (w->ut_type <= UTMAXTYPE))
2180Sstevel@tonic-gate 		return (1);
2190Sstevel@tonic-gate 	else {
220*7003Srm88369 		(void) fprintf(stderr, "Bad temp file at offset %lld\n",
221*7003Srm88369 		    (longlong_t)(ftell(f) - UTRSZ));
222*7003Srm88369 		/*
223*7003Srm88369 		 * If input was corrupt, neither ut_line nor ut_user can be
224*7003Srm88369 		 * relied on to be \0-terminated.  Even fixing the precision
225*7003Srm88369 		 * does not entirely guard against this.
226*7003Srm88369 		 */
227*7003Srm88369 		(void) fprintf(stderr,
228*7003Srm88369 		    "ut_line \"%-12.12s\" ut_user \"%-8.8s\" ut_xtime %ld\n",
229*7003Srm88369 		    w->ut_line, w->ut_user, (long)w->ut_xtime);
230*7003Srm88369 		exit(EXIT_FAILURE);
2310Sstevel@tonic-gate 	}
2320Sstevel@tonic-gate 	/* NOTREACHED */
2330Sstevel@tonic-gate }
2340Sstevel@tonic-gate 
2350Sstevel@tonic-gate static void
mkdtab(off_t p)2360Sstevel@tonic-gate mkdtab(off_t p)
2370Sstevel@tonic-gate {
2380Sstevel@tonic-gate 
2390Sstevel@tonic-gate 	struct dtab *dp;
2400Sstevel@tonic-gate 
2410Sstevel@tonic-gate 	dp = Ldp;
2420Sstevel@tonic-gate 	if (dp == NULL) {
2430Sstevel@tonic-gate 		dp = calloc(sizeof (struct dtab), 1);
2440Sstevel@tonic-gate 		if (dp == NULL) {
245*7003Srm88369 			(void) fprintf(stderr, "out of memory\n");
246*7003Srm88369 			exit(EXIT_FAILURE);
2470Sstevel@tonic-gate 		}
2480Sstevel@tonic-gate 		Fdp = Ldp = dp;
2490Sstevel@tonic-gate 	}
2500Sstevel@tonic-gate 	dp->d_off1 = p;
2510Sstevel@tonic-gate }
2520Sstevel@tonic-gate 
2530Sstevel@tonic-gate static void
setdtab(off_t p,struct futmpx * w1,struct futmpx * w2)254*7003Srm88369 setdtab(off_t p, struct futmpx *w1, struct futmpx *w2)
2550Sstevel@tonic-gate {
2560Sstevel@tonic-gate 	struct dtab *dp;
2570Sstevel@tonic-gate 
2580Sstevel@tonic-gate 	if ((dp = Ldp) == NULL) {
259*7003Srm88369 		(void) fprintf(stderr, "no dtab\n");
260*7003Srm88369 		exit(EXIT_FAILURE);
2610Sstevel@tonic-gate 	}
2620Sstevel@tonic-gate 	dp->d_off2 = p;
2630Sstevel@tonic-gate 	dp->d_adj = w2->ut_xtime - w1->ut_xtime;
2640Sstevel@tonic-gate 	if ((Ldp = calloc(sizeof (struct dtab), 1)) == NULL) {
265*7003Srm88369 		(void) fprintf(stderr, "out of memory\n");
266*7003Srm88369 		exit(EXIT_FAILURE);
2670Sstevel@tonic-gate 	}
2680Sstevel@tonic-gate 	Ldp->d_off1 = dp->d_off1;
2690Sstevel@tonic-gate 	dp->d_ndp = Ldp;
2700Sstevel@tonic-gate }
2710Sstevel@tonic-gate 
2720Sstevel@tonic-gate static void
adjust(off_t p,struct futmpx * w)273*7003Srm88369 adjust(off_t p, struct futmpx *w)
2740Sstevel@tonic-gate {
2750Sstevel@tonic-gate 
2760Sstevel@tonic-gate 	off_t pp;
2770Sstevel@tonic-gate 	struct dtab *dp;
2780Sstevel@tonic-gate 
2790Sstevel@tonic-gate 	pp = p;
2800Sstevel@tonic-gate 
2810Sstevel@tonic-gate 	for (dp = Fdp; dp != NULL; dp = dp->d_ndp) {
2820Sstevel@tonic-gate 		if (dp->d_adj == 0)
2830Sstevel@tonic-gate 			continue;
284*7003Srm88369 		if (pp >= dp->d_off1 && pp <= dp->d_off2)
2850Sstevel@tonic-gate 			w->ut_xtime += dp->d_adj;
2860Sstevel@tonic-gate 	}
2870Sstevel@tonic-gate }
2880Sstevel@tonic-gate 
2890Sstevel@tonic-gate /*
290*7003Srm88369  * invalid() determines whether the name field adheres to the criteria
291*7003Srm88369  * set forth in acctcon1.  If returns VALID if the name is ok, or
292*7003Srm88369  * INVALID if the name violates conventions.
2930Sstevel@tonic-gate  */
2940Sstevel@tonic-gate 
2950Sstevel@tonic-gate static int
invalid(char * name)2960Sstevel@tonic-gate invalid(char *name)
2970Sstevel@tonic-gate {
2980Sstevel@tonic-gate 	int	i;
2990Sstevel@tonic-gate 
3000Sstevel@tonic-gate 	for (i = 0; i < NSZ; i++) {
3010Sstevel@tonic-gate 		if (name[i] == '\0')
3020Sstevel@tonic-gate 			return (VALID);
3030Sstevel@tonic-gate 		if (! (isalnum(name[i]) || (name[i] == '$') ||
3040Sstevel@tonic-gate 		    (name[i] == ' ') || (name[i] == '.') ||
3050Sstevel@tonic-gate 		    (name[i] == '_') || (name[i] == '-'))) {
3060Sstevel@tonic-gate 			return (INVALID);
3070Sstevel@tonic-gate 		}
3080Sstevel@tonic-gate 	}
3090Sstevel@tonic-gate 	return (VALID);
3100Sstevel@tonic-gate }
3110Sstevel@tonic-gate 
3120Sstevel@tonic-gate /*
3130Sstevel@tonic-gate  * scanfile:
314*7003Srm88369  * 1)  	reads the current input file
315*7003Srm88369  * 2)   filters for process records in time range of interest and for
316*7003Srm88369  *      other types of records deemed interesting to acctcon downstream
317*7003Srm88369  * 3)   picks up time changes with setdtab() if in multiuser mode, which
318*7003Srm88369  *      will be applied when the temp file is read back
319*7003Srm88369  * 4)   changes bad login names to INVALID
320*7003Srm88369  * 5)   recovers from common cases of wtmpx corruption (loss of record
321*7003Srm88369  *      alignment).
322*7003Srm88369  * All of the static globals are used directly or indirectly.
323*7003Srm88369  *
324*7003Srm88369  * When wtmpfix is asked to process several input files in succession,
325*7003Srm88369  * some state needs to be preserved from one scanfile() invocation to the
326*7003Srm88369  * next.  Aside from the temp file position, we remember whether we were
327*7003Srm88369  * in multi-user mode or not.  Absent evidence to the contrary, we begin
328*7003Srm88369  * processing assuming multi-user mode, because runacct's wtmpx rotation
329*7003Srm88369  * normally gives us a file recently initialized by utmp2wtmp(1M) with no
330*7003Srm88369  * older RUN_LVL records surviving.
3310Sstevel@tonic-gate  */
3320Sstevel@tonic-gate 
3330Sstevel@tonic-gate static void
scanfile()3340Sstevel@tonic-gate scanfile()
3350Sstevel@tonic-gate {
336*7003Srm88369 	struct stat Wtstat;
337*7003Srm88369 	off_t residue = 0;	/* input file size mod UTRSZ */
338*7003Srm88369 	/*
339*7003Srm88369 	 * lastok will be the offset of the beginning of the most recent
340*7003Srm88369 	 * manifestly plausible and interesting input record in the current
341*7003Srm88369 	 * input file, if any.
342*7003Srm88369 	 * An invariant at loop entry is -UTRSZ <= lastok <= recin - UTRSZ.
343*7003Srm88369 	 */
344*7003Srm88369 	off_t lastok = -(off_t)UTRSZ;
345*7003Srm88369 	static off_t rectmp;	/* current temp file position */
346*7003Srm88369 	static boolean_t multimode = B_TRUE; /* multi-user RUN_LVL in force */
347*7003Srm88369 	inrange_t is_ok;	/* caches inrange() result */
348*7003Srm88369 	/*
349*7003Srm88369 	 * During normal operation, records are of interest and copied to
350*7003Srm88369 	 * the output when is_ok >= INRANGE_PASS, ignored and dropped when
351*7003Srm88369 	 * is_ok == INRANGE_DROP, and evidence of corruption otherwise.
352*7003Srm88369 	 * While we are trying to recover from a corruption and hunting for
353*7003Srm88369 	 * records with sufficient redundancy to confirm that we have reached
354*7003Srm88369 	 * proper alignment again, we'll want is_ok >= INRANGE_ALIGNED.
355*7003Srm88369 	 * The value of want_ok is the minimum inrange() result of current
356*7003Srm88369 	 * interest.  It is raised to INRANGE_ALIGNED during ongoing recovery
357*7003Srm88369 	 * and dropped back to INRANGE_PASS when we have recovered alignment.
358*7003Srm88369 	 */
359*7003Srm88369 	inrange_t want_ok = INRANGE_PASS;
360*7003Srm88369 	boolean_t recovered = B_FALSE; /* true after a successful recovery */
361*7003Srm88369 	int n;
362*7003Srm88369 
363*7003Srm88369 	if (fstat(fileno(Wtmpx), &Wtstat) == -1) {
364*7003Srm88369 		(void) fprintf(stderr,
365*7003Srm88369 		    "Cannot stat %s (will read sequentially): %s\n",
366*7003Srm88369 		    cur_input_name, strerror(errno));
367*7003Srm88369 	} else if ((Wtstat.st_mode & S_IFMT) == S_IFREG) {
368*7003Srm88369 		residue = Wtstat.st_size % UTRSZ;
369*7003Srm88369 	}
370*7003Srm88369 
371*7003Srm88369 	/* if residue != 0, part of the file may be misaligned */
372*7003Srm88369 	for (recin = 0;
373*7003Srm88369 	    ((n = fread(&Ut, (size_t)UTRSZ, (size_t)1, Wtmpx)) > 0) ||
374*7003Srm88369 	    (residue > 0);
375*7003Srm88369 	    recin += UTRSZ) {
3760Sstevel@tonic-gate 		if (n == 0) {
377*7003Srm88369 			/*
378*7003Srm88369 			 * Implying residue > 0 and want_ok == INRANGE_PASS.
379*7003Srm88369 			 * It isn't worth telling an I/O error from EOF here.
380*7003Srm88369 			 * But one case is worth catching to avoid issuing a
381*7003Srm88369 			 * confusing message below.  When the previous record
382*7003Srm88369 			 * had been ok, we just drop the current truncated
383*7003Srm88369 			 * record and bail out of the loop -- no seeking back.
384*7003Srm88369 			 */
385*7003Srm88369 			if (lastok == recin - UTRSZ) {
386*7003Srm88369 				wcomplain("file ends in mid-record, "
387*7003Srm88369 				    "final partial record dropped");
388*7003Srm88369 				break;
389*7003Srm88369 			} else {
390*7003Srm88369 				wcomplain("file ends in mid-record");
391*7003Srm88369 				/* handled below like a corrupted record */
392*7003Srm88369 				is_ok = INRANGE_ERR;
3930Sstevel@tonic-gate 			}
394*7003Srm88369 		} else
395*7003Srm88369 			is_ok = inrange();
3960Sstevel@tonic-gate 
397*7003Srm88369 		/* alignment recovery logic */
398*7003Srm88369 		if ((residue > 0) && (is_ok == INRANGE_ERR)) {
399*7003Srm88369 			/*
400*7003Srm88369 			 * "Let's go back to the last place where we knew
401*7003Srm88369 			 * where we were..."
402*7003Srm88369 			 * In fact, if the last record had been fine and we
403*7003Srm88369 			 * know there's at least one whole record ahead, we
404*7003Srm88369 			 * might move forward here  (by residue bytes, less
405*7003Srm88369 			 * than one record's worth).  In any case, we align
406*7003Srm88369 			 * ourselves to an integral number of records before
407*7003Srm88369 			 * the end of the file.
408*7003Srm88369 			 */
409*7003Srm88369 			wcomplain("suspecting misaligned records, "
410*7003Srm88369 			    "repositioning");
411*7003Srm88369 			recin = lastok + UTRSZ + residue;
412*7003Srm88369 			residue = 0;
413*7003Srm88369 			if (fseeko(Wtmpx, recin, SEEK_SET) != 0) {
414*7003Srm88369 				(void) fprintf(stderr, "%s: seek: %s\n",
415*7003Srm88369 				    cur_input_name, strerror(errno));
416*7003Srm88369 				exit(EXIT_FAILURE);
4170Sstevel@tonic-gate 			}
418*7003Srm88369 			wcomplain("starting re-scan");
419*7003Srm88369 			/*
420*7003Srm88369 			 * While want_ok is elevated, only unequivocal records
421*7003Srm88369 			 * with inrange() == INRANGE_ALIGNED will be admitted
422*7003Srm88369 			 * to latch onto the tentative new alignment.
423*7003Srm88369 			 */
424*7003Srm88369 			want_ok = INRANGE_ALIGNED;
425*7003Srm88369 			/*
426*7003Srm88369 			 * Compensate for the loop continuation.  Doing
427*7003Srm88369 			 * it this way gets the correct offset reported
428*7003Srm88369 			 * in the re-scan message above.
429*7003Srm88369 			 */
430*7003Srm88369 			recin -= UTRSZ;
431*7003Srm88369 			continue;
432*7003Srm88369 		}
433*7003Srm88369 		/* assert: residue == 0 or is_ok >= INRANGE_DROP here */
434*7003Srm88369 		if (is_ok < want_ok)
435*7003Srm88369 			/* record of no further interest */
436*7003Srm88369 			continue;
437*7003Srm88369 		if (want_ok == INRANGE_ALIGNED) {
438*7003Srm88369 			wcomplain("now recognizing aligned records again");
439*7003Srm88369 			want_ok = INRANGE_PASS;
440*7003Srm88369 			recovered = B_TRUE;
441*7003Srm88369 		}
442*7003Srm88369 		/*
443*7003Srm88369 		 * lastok must track recin whenever the current record is
444*7003Srm88369 		 * being processed and written out to our temp file, to avoid
445*7003Srm88369 		 * reprocessing any bits already done when we readjust our
446*7003Srm88369 		 * alignment.
447*7003Srm88369 		 */
448*7003Srm88369 		lastok = recin;
449*7003Srm88369 
450*7003Srm88369 		/* now we have a good wtmpx record, do more processing */
451*7003Srm88369 
452*7003Srm88369 		if (rectmp == 0 || Ut.ut_type == BOOT_TIME)
453*7003Srm88369 			mkdtab(rectmp);
454*7003Srm88369 		if (Ut.ut_type == RUN_LVL) {
455*7003Srm88369 			/* inrange() already checked the "run-level " part */
456*7003Srm88369 			if (Ut.ut_line[RLVLMSG_LEN] == 'S')
457*7003Srm88369 				multimode = B_FALSE;
458*7003Srm88369 			else if ((Ut.ut_line[RLVLMSG_LEN] == '2') ||
459*7003Srm88369 			    (Ut.ut_line[RLVLMSG_LEN] == '3') ||
460*7003Srm88369 			    (Ut.ut_line[RLVLMSG_LEN] == '4'))
461*7003Srm88369 				multimode = B_TRUE;
462*7003Srm88369 		}
463*7003Srm88369 		if (invalid(Ut.ut_name) == INVALID) {
464*7003Srm88369 			(void) fprintf(stderr,
465*7003Srm88369 			    "wtmpfix: logname \"%*.*s\" changed "
466*7003Srm88369 			    "to \"INVALID\"\n", OUTPUT_NSZ,
467*7003Srm88369 			    OUTPUT_NSZ, Ut.ut_name);
468*7003Srm88369 			(void) strncpy(Ut.ut_name, "INVALID", NSZ);
469*7003Srm88369 		}
470*7003Srm88369 		/*
471*7003Srm88369 		 * Special case: OLD_TIME should be immediately followed by
472*7003Srm88369 		 * NEW_TIME.
473*7003Srm88369 		 * We make no attempt at alignment recovery between these
474*7003Srm88369 		 * two: if there's junk at this point in the input, then
475*7003Srm88369 		 * a NEW_TIME seen after the junk probably won't be the one
476*7003Srm88369 		 * we are looking for.
477*7003Srm88369 		 */
478*7003Srm88369 		if (Ut.ut_type == OLD_TIME) {
479*7003Srm88369 			/*
480*7003Srm88369 			 * Make recin refer to the expected NEW_TIME.
481*7003Srm88369 			 * Loop continuation will increment it again
482*7003Srm88369 			 * for the record we're about to read now.
483*7003Srm88369 			 */
484*7003Srm88369 			recin += UTRSZ;
485*7003Srm88369 			if (!fread(&Ut2, (size_t)UTRSZ, (size_t)1, Wtmpx)) {
486*7003Srm88369 				wcomplain("input truncated after OLD_TIME - "
487*7003Srm88369 				    "giving up");
488*7003Srm88369 				exit(EXIT_FAILURE);
4890Sstevel@tonic-gate 			}
490*7003Srm88369 			/*
491*7003Srm88369 			 * Rudimentary NEW_TIME sanity check.  Not as thorough
492*7003Srm88369 			 * as in inrange(), but then we have redundancy from
493*7003Srm88369 			 * context here, since we're just after a plausible
494*7003Srm88369 			 * OLD_TIME record.
495*7003Srm88369 			 */
496*7003Srm88369 			if ((Ut2.ut_type != NEW_TIME) ||
497*7003Srm88369 			    (strcmp(Ut2.ut_line, NTIME_MSG) != 0)) {
498*7003Srm88369 				wcomplain("NEW_TIME expected but missing "
499*7003Srm88369 				    "after OLD_TIME - giving up");
500*7003Srm88369 				exit(EXIT_FAILURE);
501*7003Srm88369 			}
502*7003Srm88369 			lastok = recin;
503*7003Srm88369 			if (multimode == B_TRUE)
504*7003Srm88369 				setdtab(rectmp, &Ut, &Ut2);
505*7003Srm88369 			rectmp += 2 * UTRSZ;
506*7003Srm88369 			if ((fwrite(&Ut, UTRSZ, 1, Temp) < 1) ||
507*7003Srm88369 			    (fwrite(&Ut2, UTRSZ, 1, Temp) < 1)) {
508*7003Srm88369 				perror("<temporary file>: fwrite");
509*7003Srm88369 				exit(EXIT_FAILURE);
510*7003Srm88369 			}
511*7003Srm88369 			continue;
512*7003Srm88369 		}
513*7003Srm88369 		if (fwrite(&Ut, UTRSZ, 1, Temp) < 1) {
514*7003Srm88369 			perror("<temporary file>: fwrite");
515*7003Srm88369 			exit(EXIT_FAILURE);
516*7003Srm88369 		}
517*7003Srm88369 		rectmp += UTRSZ;
518*7003Srm88369 	}
519*7003Srm88369 	if (want_ok == INRANGE_ALIGNED) {
520*7003Srm88369 		wcomplain("EOF reached without recognizing another aligned "
521*7003Srm88369 		    "record with certainty. This file may need to be "
522*7003Srm88369 		    "repaired by hand.\n");
523*7003Srm88369 	} else if (recovered == B_TRUE) {
524*7003Srm88369 		/*
525*7003Srm88369 		 * There may have been a number of wcomplain() messages
526*7003Srm88369 		 * since we reported about the re-scan, so it bears repeating
527*7003Srm88369 		 * at the end that not all was well.
528*7003Srm88369 		 */
529*7003Srm88369 		wcomplain("EOF reached after recovering from corruption "
530*7003Srm88369 		    "in the middle of the file.  This file may need to be "
531*7003Srm88369 		    "repaired by hand.\n");
5320Sstevel@tonic-gate 	}
5330Sstevel@tonic-gate }
5340Sstevel@tonic-gate 
535*7003Srm88369 /*
536*7003Srm88369  * inrange: inspect what we hope to be one wtmpx record.
537*7003Srm88369  * Globals:  Ut, lastmonth, nextmonth;  recin, cur_input_name (diagnostics)
538*7003Srm88369  * Return values:
539*7003Srm88369  * INRANGE_ERR     -- an inconsistency was detected, input file corrupted
540*7003Srm88369  * INRANGE_DROP    -- Ut appears consistent but isn't of interest
541*7003Srm88369  *                    (of process type and outside the time range we want)
542*7003Srm88369  * INRANGE_PASS    -- Ut appears consistent and this record is of interest
543*7003Srm88369  * INRANGE_ALIGNED -- same, and it is also redundant enough to be sure
544*7003Srm88369  *                    that we're correctly aligned on record boundaries
545*7003Srm88369  */
546*7003Srm88369 #define	UNEXPECTED_UT_PID \
547*7003Srm88369 	(Ut.ut_pid != 0) || \
548*7003Srm88369 	(Ut.ut_exit.e_termination != 0) || \
549*7003Srm88369 	(Ut.ut_exit.e_exit != 0)
550*7003Srm88369 
551*7003Srm88369 static inrange_t
inrange()5520Sstevel@tonic-gate inrange()
5530Sstevel@tonic-gate {
554*7003Srm88369 	/* pid_t is signed so that fork() can return -1.  Exploit this. */
555*7003Srm88369 	if (Ut.ut_pid < 0) {
556*7003Srm88369 		wcomplain("negative pid");
557*7003Srm88369 		return (INRANGE_ERR);
558*7003Srm88369 	}
559*7003Srm88369 
560*7003Srm88369 	/* the legal values for ut_type are enumerated in <utmp.h> */
561*7003Srm88369 	switch (Ut.ut_type) {
562*7003Srm88369 	case EMPTY:
563*7003Srm88369 		if (UNEXPECTED_UT_PID) {
564*7003Srm88369 			wcomplain("nonzero pid or status in EMPTY record");
565*7003Srm88369 			return (INRANGE_ERR);
566*7003Srm88369 		}
567*7003Srm88369 		/*
568*7003Srm88369 		 * We'd like to have Ut.ut_user[0] == '\0' here, but sadly
569*7003Srm88369 		 * this isn't always so, so we can't rely on it.
570*7003Srm88369 		 */
571*7003Srm88369 		return (INRANGE_DROP);
572*7003Srm88369 	case RUN_LVL:
573*7003Srm88369 		/* ut_line must have come from the RUNLVL_MSG pattern */
574*7003Srm88369 		if (strncmp(Ut.ut_line, RUN_LEVEL_MSG, RLVLMSG_LEN) != 0) {
575*7003Srm88369 			wcomplain("RUN_LVL record doesn't say `"
576*7003Srm88369 			    RUN_LEVEL_MSG "'");
577*7003Srm88369 			return (INRANGE_ERR);
578*7003Srm88369 		}
579*7003Srm88369 		/*
580*7003Srm88369 		 * The ut_pid, termination, and exit status fields have
581*7003Srm88369 		 * special meaning in this case, and none of them is
582*7003Srm88369 		 * suitable for checking.  And we won't insist on ut_user
583*7003Srm88369 		 * to always be an empty string.
584*7003Srm88369 		 */
585*7003Srm88369 		return (INRANGE_ALIGNED);
586*7003Srm88369 	case BOOT_TIME:
587*7003Srm88369 		if (UNEXPECTED_UT_PID) {
588*7003Srm88369 			wcomplain("nonzero pid or status in BOOT_TIME record");
589*7003Srm88369 			return (INRANGE_ERR);
590*7003Srm88369 		}
591*7003Srm88369 		if (strcmp(Ut.ut_line, BOOT_MSG) != 0) {
592*7003Srm88369 			wcomplain("BOOT_TIME record doesn't say `"
593*7003Srm88369 			    BOOT_MSG "'");
594*7003Srm88369 			return (INRANGE_ERR);
595*7003Srm88369 		}
596*7003Srm88369 		return (INRANGE_ALIGNED);
597*7003Srm88369 	case OLD_TIME:
598*7003Srm88369 		if (UNEXPECTED_UT_PID) {
599*7003Srm88369 			wcomplain("nonzero pid or status in OLD_TIME record");
600*7003Srm88369 			return (INRANGE_ERR);
601*7003Srm88369 		}
602*7003Srm88369 		if (strcmp(Ut.ut_line, OTIME_MSG) != 0) {
603*7003Srm88369 			wcomplain("OLD_TIME record doesn't say `"
604*7003Srm88369 			    OTIME_MSG "'");
605*7003Srm88369 			return (INRANGE_ERR);
606*7003Srm88369 		}
607*7003Srm88369 		return (INRANGE_ALIGNED);
608*7003Srm88369 	case NEW_TIME:
609*7003Srm88369 		/*
610*7003Srm88369 		 * We don't actually expect to see any here.  If they follow
611*7003Srm88369 		 * an OLD_TIME record as they should, they'll be handled on
612*7003Srm88369 		 * the fly in scanfile().  But we might still run into one
613*7003Srm88369 		 * if the input is somehow corrupted.
614*7003Srm88369 		 */
615*7003Srm88369 		if (UNEXPECTED_UT_PID) {
616*7003Srm88369 			wcomplain("nonzero pid or status in NEW_TIME record");
617*7003Srm88369 			return (INRANGE_ERR);
618*7003Srm88369 		}
619*7003Srm88369 		if (strcmp(Ut.ut_line, NTIME_MSG) != 0) {
620*7003Srm88369 			wcomplain("NEW_TIME record doesn't say `"
621*7003Srm88369 			    NTIME_MSG "'");
622*7003Srm88369 			return (INRANGE_ERR);
623*7003Srm88369 		}
624*7003Srm88369 		return (INRANGE_ALIGNED);
6250Sstevel@tonic-gate 
626*7003Srm88369 	/* the four *_PROCESS ut_types have a lot in common */
627*7003Srm88369 	case USER_PROCESS:
628*7003Srm88369 		/*
629*7003Srm88369 		 * Catch two special cases first: psradm records have no id
630*7003Srm88369 		 * and no pid, while root login over FTP may not have a
631*7003Srm88369 		 * valid ut_user and may have garbage in ut_id[3].
632*7003Srm88369 		 */
633*7003Srm88369 		if ((strcmp(Ut.ut_user, "psradm") == 0) &&
634*7003Srm88369 		    (Ut.ut_id[0] == '\0') &&
635*7003Srm88369 		    (Ut.ut_pid > 0)) {
636*7003Srm88369 			if ((Ut.ut_xtime > lastmonth) &&
637*7003Srm88369 			    (Ut.ut_xtime < nextmonth)) {
638*7003Srm88369 				return (INRANGE_ALIGNED);
639*7003Srm88369 			} else {
640*7003Srm88369 				return (INRANGE_DROP);
641*7003Srm88369 			}
642*7003Srm88369 		}
643*7003Srm88369 		if ((Ut.ut_user[0] == '\0') &&
644*7003Srm88369 		    (strncmp(Ut.ut_id, "ftp", 3) == 0) &&
645*7003Srm88369 		    (strncmp(Ut.ut_line, "ftp", 3) == 0)) {
646*7003Srm88369 			if ((Ut.ut_xtime > lastmonth) &&
647*7003Srm88369 			    (Ut.ut_xtime < nextmonth)) {
648*7003Srm88369 				return (INRANGE_ALIGNED);
649*7003Srm88369 			} else {
650*7003Srm88369 				return (INRANGE_DROP);
651*7003Srm88369 			}
652*7003Srm88369 		}
653*7003Srm88369 		/* FALLTHROUGH */
654*7003Srm88369 	case LOGIN_PROCESS:
655*7003Srm88369 		if (Ut.ut_user[0] == '\0') {
656*7003Srm88369 			wcomplain("missing username in process record");
657*7003Srm88369 			return (INRANGE_ERR);
658*7003Srm88369 		}
659*7003Srm88369 		/* FALLTHROUGH */
660*7003Srm88369 	case INIT_PROCESS:
661*7003Srm88369 		/*
662*7003Srm88369 		 * INIT_PROCESS and DEAD_PROCESS records can come with an
663*7003Srm88369 		 * empty ut_user in degenerate cases (e.g. syntax errors
664*7003Srm88369 		 * like a comment-only process field in /etc/inittab).
665*7003Srm88369 		 * But in an INIT_PROCESS, LOGIN_PROCESS, or USER_PROCESS
666*7003Srm88369 		 * record, we expect a respectable ut_pid.
667*7003Srm88369 		 */
668*7003Srm88369 		if (Ut.ut_pid == 0) {
669*7003Srm88369 			wcomplain("null pid in process record");
670*7003Srm88369 			return (INRANGE_ERR);
671*7003Srm88369 		}
672*7003Srm88369 		/* FALLTHROUGH */
673*7003Srm88369 	case DEAD_PROCESS:
674*7003Srm88369 		/*
675*7003Srm88369 		 * DEAD_PROCESS records with a null ut_pid can be produced
676*7003Srm88369 		 * by gnome-terminal (normally seen in utmpx only, but they
677*7003Srm88369 		 * can leak into wtmpx in rare circumstances).
678*7003Srm88369 		 * Unfortunately, ut_id can't be relied on to contain
679*7003Srm88369 		 * anything in particular.  (E.g., sshd might leave it
680*7003Srm88369 		 * 0-initialized.)  This leaves almost no verifiable
681*7003Srm88369 		 * redundancy here beyond the ut_type.
682*7003Srm88369 		 * At least we insist on a reasonable timestamp.
683*7003Srm88369 		 */
684*7003Srm88369 		if (Ut.ut_xtime <= 0) {
685*7003Srm88369 			wcomplain("non-positive time in process record");
686*7003Srm88369 			return (INRANGE_ERR);
687*7003Srm88369 		}
688*7003Srm88369 		if ((Ut.ut_xtime > lastmonth) &&
689*7003Srm88369 		    (Ut.ut_xtime < nextmonth)) {
690*7003Srm88369 			return (INRANGE_PASS);
691*7003Srm88369 		} else {
692*7003Srm88369 			return (INRANGE_DROP);
693*7003Srm88369 		}
694*7003Srm88369 	case ACCOUNTING:
695*7003Srm88369 		/*
696*7003Srm88369 		 * If we recognize one of the three reason strings passed
697*7003Srm88369 		 * by the /usr/lib/acct shell scripts to acctwtmp, we
698*7003Srm88369 		 * exploit the available redundancy they offer.  But
699*7003Srm88369 		 * acctwtmp could have been invoked by custom scripts or
700*7003Srm88369 		 * interactively with other reason strings in the first
701*7003Srm88369 		 * argument, so anything we don't recognize does not
702*7003Srm88369 		 * constitute evidence for corruption.
703*7003Srm88369 		 */
704*7003Srm88369 		if ((strcmp(Ut.ut_line, RUNACCT_MSG) != 0) &&
705*7003Srm88369 		    (strcmp(Ut.ut_line, ACCTG_ON_MSG) != 0) &&
706*7003Srm88369 		    (strcmp(Ut.ut_line, ACCTG_OFF_MSG) != 0)) {
707*7003Srm88369 			return (INRANGE_DROP);
708*7003Srm88369 		}
709*7003Srm88369 		return (INRANGE_ALIGNED);
710*7003Srm88369 	case DOWN_TIME:
711*7003Srm88369 		if (UNEXPECTED_UT_PID) {
712*7003Srm88369 			wcomplain("nonzero pid or status in DOWN_TIME record");
713*7003Srm88369 			return (INRANGE_ERR);
714*7003Srm88369 		}
715*7003Srm88369 		if (strcmp(Ut.ut_line, DOWN_MSG) != 0) {
716*7003Srm88369 			wcomplain("DOWN_TIME record doesn't say `"
717*7003Srm88369 			    DOWN_MSG "'");
718*7003Srm88369 			return (INRANGE_ERR);
719*7003Srm88369 		}
720*7003Srm88369 		return (INRANGE_ALIGNED);
721*7003Srm88369 	default:
722*7003Srm88369 		wcomplain("ut_type out of range");
723*7003Srm88369 		return (INRANGE_ERR);
724*7003Srm88369 	}
725*7003Srm88369 	/* NOTREACHED */
7260Sstevel@tonic-gate }
7270Sstevel@tonic-gate 
7280Sstevel@tonic-gate static void
wcomplain(char * msg)729*7003Srm88369 wcomplain(char *msg)
7300Sstevel@tonic-gate {
731*7003Srm88369 	(void) fprintf(stderr, "%s: offset %lld: %s\n", cur_input_name,
732*7003Srm88369 	    (longlong_t)recin, msg);
7330Sstevel@tonic-gate }
734