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