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 */
83typedef 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>"
92static char	*cur_input_name;
93static off_t	recin;
94
95static FILE	*Wtmpx, *Temp;
96
97struct	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
105static struct	dtab	*Fdp;	/* list header */
106static struct	dtab	*Ldp;	/* list trailer */
107
108static time_t 	lastmonth, nextmonth;
109
110static struct	futmpx	Ut, Ut2;
111
112static int winp(FILE *, struct futmpx *);
113static void mkdtab(off_t);
114static void setdtab(off_t, struct futmpx *, struct futmpx *);
115static void adjust(off_t, struct futmpx *);
116static int invalid(char *);
117static void scanfile(void);
118static inrange_t inrange(void);
119static void wcomplain(char *);
120
121int
122main(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
212static int
213winp(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
235static void
236mkdtab(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
253static void
254setdtab(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
272static void
273adjust(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
295static int
296invalid(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
333static void
334scanfile()
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
551static inrange_t
552inrange()
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
728static void
729wcomplain(char *msg)
730{
731	(void) fprintf(stderr, "%s: offset %lld: %s\n", cur_input_name,
732	    (longlong_t)recin, msg);
733}
734