summaryrefslogtreecommitdiff
path: root/usr/src
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src')
-rw-r--r--usr/src/cmd/acct/wtmpfix.c717
1 files changed, 537 insertions, 180 deletions
diff --git a/usr/src/cmd/acct/wtmpfix.c b/usr/src/cmd/acct/wtmpfix.c
index 56ad06c2ad..d5a7ac3a95 100644
--- a/usr/src/cmd/acct/wtmpfix.c
+++ b/usr/src/cmd/acct/wtmpfix.c
@@ -2,9 +2,8 @@
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License"). You may not use this file except in compliance
- * with the License.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
@@ -24,38 +23,76 @@
/* All Rights Reserved */
/*
- * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
+
/*
* wtmpfix - adjust wtmpx file and remove date changes.
* wtmpfix <wtmpx1 >wtmpx2
*
- * code are added to really fix wtmpx if it is corrupted ..
+ * Can recover to some extent from wtmpx corruption.
*/
#include <stdio.h>
#include <sys/types.h>
+#include <sys/stat.h>
#include <sys/param.h>
#include "acctdef.h"
#include <utmpx.h>
-#include <signal.h>
#include <time.h>
#include <ctype.h>
#include <locale.h>
-#include <limits.h>
#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
-#define MAXRUNTIME 3600 /* time out after 1 hour */
#define DAYEPOCH (60 * 60 * 24)
-#define wout(f, w) fwrite(w, sizeof (struct utmpx), 1, f);
+#define UTRSZ (sizeof (struct futmpx)) /* file record size */
+
+/*
+ * The acctsh(1M) shell scripts startup(1M) and shutacct(1M) as well as the
+ * runacct script each pass their own specific reason strings in the first
+ * argument to acctwtmp(1M), to be propagated into ut_line fields. Additional
+ * reasons (RUNLVL_MSG, ..., DOWN_MSG), used by compiled code, are defined in
+ * <utmp.h> as preprocessor constants.
+ * For simplicity we predefine similar constants for the scripted strings
+ * here, as no other compiled code uses those.
+ * Moreover, we need a variant of RUNLVL_MSG without the "%c" at the end.
+ * We shall use the fact that ut_line[RLVLMSG_LEN] will extract the char
+ * in the %c position ('S', '2', ...).
+ * Since all of these string constants are '\0' terminated, they can safely
+ * be used with strcmp() even when ut_line is not.
+ */
+#define RUN_LEVEL_MSG "run-level "
+#define ACCTG_ON_MSG "acctg on"
+#define ACCTG_OFF_MSG "acctg off"
+#define RUNACCT_MSG "runacct"
+
+#define RLVLMSG_LEN (sizeof (RUN_LEVEL_MSG) - 1)
+
+/*
+ * Records encountered are classified as one of the following: corrupted;
+ * ok but devoid of interest to acctcon downstream; ok and interesting;
+ * or ok and even redundant enough to latch onto a new alignment whilst
+ * recovering from a corruption.
+ * The ordering among these four symbolic values is significant.
+ */
+typedef enum {
+ INRANGE_ERR = -1,
+ INRANGE_DROP,
+ INRANGE_PASS,
+ INRANGE_ALIGNED
+} inrange_t;
+
+/* input filenames and record numbers, for diagnostics only */
+#define STDIN_NAME "<stdin>"
+static char *cur_input_name;
+static off_t recin;
-FILE *Wtmpx, *Opw;
-FILE *fp;
-char Ofile[] = "/tmp/wXXXXXX";
-static char time_buf[50];
+static FILE *Wtmpx, *Temp;
struct dtab
{
@@ -65,50 +102,35 @@ struct dtab
struct dtab *d_ndp; /* next record */
};
-struct dtab *Fdp; /* list header */
-struct dtab *Ldp; /* list trailer */
+static struct dtab *Fdp; /* list header */
+static struct dtab *Ldp; /* list trailer */
-time_t lastmonth, nextmonth;
-off_t recno;
+static time_t lastmonth, nextmonth;
-struct utmpx Ut, Ut2;
+static struct futmpx Ut, Ut2;
-int year, month;
-int ch;
-int n;
-int multimode; /* multi user mode WHCC */
-
-static int winp(FILE *, struct utmpx *);
+static int winp(FILE *, struct futmpx *);
static void mkdtab(off_t);
-static void setdtab(off_t, struct utmpx *, struct utmpx *);
-static void adjust(off_t, struct utmpx *);
+static void setdtab(off_t, struct futmpx *, struct futmpx *);
+static void adjust(off_t, struct futmpx *);
static int invalid(char *);
-static void intr(int) __NORETURN;
static void scanfile(void);
-static int inrange(void);
-static void wabort(int);
+static inrange_t inrange(void);
+static void wcomplain(char *);
int
main(int argc, char **argv)
{
time_t tloc;
struct tm *tmp;
- int fd;
+ int year;
+ int month;
+ off_t rectmpin;
(void) setlocale(LC_ALL, "");
setbuf(stdout, NULL);
- alarm(MAXRUNTIME);
-
- if (signal(SIGALRM, wabort) == SIG_ERR) {
- perror("signal");
- return (1);
- }
- if (signal(SIGINT, intr) == SIG_ERR) {
- perror("signal");
- return (1);
- }
- time(&tloc);
+ (void) time(&tloc);
tmp = localtime(&tloc);
year = tmp->tm_year;
month = tmp->tm_mon + 1;
@@ -122,60 +144,90 @@ main(int argc, char **argv)
argc++;
}
- if ((fd = mkstemp(Ofile)) == -1) {
- fprintf(stderr, "cannot make temporary: %s\n", Ofile);
- intr(0);
- }
-
- if ((Opw = fdopen(fd, "w")) == NULL) {
- fprintf(stderr, "cannot open temporary: %s\n", Ofile);
- intr(0);
+ /*
+ * Almost all system call failures in this program are unrecoverable
+ * and therefore fatal. Typical causes might be lack of memory or
+ * of space in a filesystem. If necessary, the system administrator
+ * can invoke /usr/lib/acct/runacct interactively after making room
+ * to complete the remaining phases of last night's accounting.
+ */
+ if ((Temp = tmpfile()) == NULL) {
+ perror("Cannot create temporary file");
+ return (EXIT_FAILURE);
}
while (--argc > 0) {
argv++;
- if (strcmp(*argv, "-") == 0)
+ if (strcmp(*argv, "-") == 0) {
Wtmpx = stdin;
- else if ((Wtmpx = fopen(*argv, "r")) == NULL) {
- fprintf(stderr, "Cannot open: %s\n", *argv);
- intr(0);
+ cur_input_name = STDIN_NAME;
+ } else if ((Wtmpx = fopen(*argv, "r")) == NULL) {
+ (void) fprintf(stderr, "Cannot open %s: %s\n",
+ *argv, strerror(errno));
+ return (EXIT_FAILURE);
+ } else {
+ cur_input_name = *argv;
}
+ /*
+ * Filter records reading from current input stream Wtmpx,
+ * writing to Temp.
+ */
scanfile();
if (Wtmpx != stdin)
- fclose(Wtmpx);
+ (void) fclose(Wtmpx);
}
- fclose(Opw);
-
- if ((Opw = fopen(Ofile, "r")) == NULL) {
- fprintf(stderr, "Cannot read from temp: %s\n", Ofile);
- intr(0);
+ /* flush and rewind Temp for readback */
+ if (fflush(Temp) != 0) {
+ perror("<temporary file>: fflush");
+ return (EXIT_FAILURE);
+ }
+ if (fseeko(Temp, (off_t)0L, SEEK_SET) != 0) {
+ perror("<temporary file>: seek");
+ return (EXIT_FAILURE);
+ }
+ /* second pass: apply time adjustments */
+ rectmpin = 0;
+ while (winp(Temp, &Ut)) {
+ adjust(rectmpin, &Ut);
+ rectmpin += UTRSZ;
+ if (fwrite(&Ut, UTRSZ, 1, stdout) < 1) {
+ perror("<stdout>: fwrite");
+ return (EXIT_FAILURE);
+ }
}
- recno = 0;
- while (winp(Opw, &Ut)) {
- adjust(recno, &Ut);
- recno += sizeof (struct utmpx);
- wout(stdout, &Ut);
+ (void) fclose(Temp);
+ /*
+ * Detect if we've run out of space (say) and exit unsuccessfully
+ * so that downstream accounting utilities won't start processing an
+ * incomplete tmpwtmp file.
+ */
+ if (fflush(stdout) != 0) {
+ perror("<stdout>: fflush");
+ return (EXIT_FAILURE);
}
- fclose(Opw);
- unlink(Ofile);
- return (0);
+ return (EXIT_SUCCESS);
}
static int
-winp(FILE *f, struct utmpx *w)
+winp(FILE *f, struct futmpx *w)
{
- if (fread(w, sizeof (struct utmpx), 1, f) != 1)
+ if (fread(w, (size_t)UTRSZ, (size_t)1, f) != 1)
return (0);
if ((w->ut_type >= EMPTY) && (w->ut_type <= UTMAXTYPE))
return (1);
else {
- fprintf(stderr, "Bad file at offset %ld\n",
- ftell(f) - sizeof (struct utmpx));
- cftime(time_buf, DATE_FMT, &w->ut_xtime);
- fprintf(stderr, "%-12s %-8s %lu %s",
- w->ut_line, w->ut_user, w->ut_xtime, time_buf);
- intr(0);
+ (void) fprintf(stderr, "Bad temp file at offset %lld\n",
+ (longlong_t)(ftell(f) - UTRSZ));
+ /*
+ * If input was corrupt, neither ut_line nor ut_user can be
+ * relied on to be \0-terminated. Even fixing the precision
+ * does not entirely guard against this.
+ */
+ (void) fprintf(stderr,
+ "ut_line \"%-12.12s\" ut_user \"%-8.8s\" ut_xtime %ld\n",
+ w->ut_line, w->ut_user, (long)w->ut_xtime);
+ exit(EXIT_FAILURE);
}
/* NOTREACHED */
}
@@ -190,8 +242,8 @@ mkdtab(off_t p)
if (dp == NULL) {
dp = calloc(sizeof (struct dtab), 1);
if (dp == NULL) {
- fprintf(stderr, "out of core\n");
- intr(0);
+ (void) fprintf(stderr, "out of memory\n");
+ exit(EXIT_FAILURE);
}
Fdp = Ldp = dp;
}
@@ -199,26 +251,26 @@ mkdtab(off_t p)
}
static void
-setdtab(off_t p, struct utmpx *w1, struct utmpx *w2)
+setdtab(off_t p, struct futmpx *w1, struct futmpx *w2)
{
struct dtab *dp;
if ((dp = Ldp) == NULL) {
- fprintf(stderr, "no dtab\n");
- intr(0);
+ (void) fprintf(stderr, "no dtab\n");
+ exit(EXIT_FAILURE);
}
dp->d_off2 = p;
dp->d_adj = w2->ut_xtime - w1->ut_xtime;
if ((Ldp = calloc(sizeof (struct dtab), 1)) == NULL) {
- fprintf(stderr, "out of core\n");
- intr(0);
+ (void) fprintf(stderr, "out of memory\n");
+ exit(EXIT_FAILURE);
}
Ldp->d_off1 = dp->d_off1;
dp->d_ndp = Ldp;
}
static void
-adjust(off_t p, struct utmpx *w)
+adjust(off_t p, struct futmpx *w)
{
off_t pp;
@@ -229,17 +281,15 @@ adjust(off_t p, struct utmpx *w)
for (dp = Fdp; dp != NULL; dp = dp->d_ndp) {
if (dp->d_adj == 0)
continue;
- if (pp >= dp->d_off1 && pp < dp->d_off2)
+ if (pp >= dp->d_off1 && pp <= dp->d_off2)
w->ut_xtime += dp->d_adj;
}
}
/*
- * invalid() determines whether the name field adheres to
- * the criteria set forth in acctcon1. If the name violates
- * conventions, it returns a truth value meaning the name is
- * invalid; if the name is okay, it returns false indicating
- * the name is not invalid.
+ * invalid() determines whether the name field adheres to the criteria
+ * set forth in acctcon1. If returns VALID if the name is ok, or
+ * INVALID if the name violates conventions.
*/
static int
@@ -259,118 +309,425 @@ invalid(char *name)
return (VALID);
}
-static void
-intr(int sig)
-{
- signal(SIGINT, SIG_IGN);
- unlink(Ofile);
- exit(1);
-}
-
/*
* scanfile:
- * 1) reads the file, to see if the record is within reasonable
- * range; if not, then it will scan the file, delete foreign stuff.
- * 2) enter setdtab if in multiuser mode
- * 3) change bad login names to INVALID
+ * 1) reads the current input file
+ * 2) filters for process records in time range of interest and for
+ * other types of records deemed interesting to acctcon downstream
+ * 3) picks up time changes with setdtab() if in multiuser mode, which
+ * will be applied when the temp file is read back
+ * 4) changes bad login names to INVALID
+ * 5) recovers from common cases of wtmpx corruption (loss of record
+ * alignment).
+ * All of the static globals are used directly or indirectly.
+ *
+ * When wtmpfix is asked to process several input files in succession,
+ * some state needs to be preserved from one scanfile() invocation to the
+ * next. Aside from the temp file position, we remember whether we were
+ * in multi-user mode or not. Absent evidence to the contrary, we begin
+ * processing assuming multi-user mode, because runacct's wtmpx rotation
+ * normally gives us a file recently initialized by utmp2wtmp(1M) with no
+ * older RUN_LVL records surviving.
*/
static void
scanfile()
{
- while ((n = fread(&Ut, sizeof (Ut), 1, Wtmpx)) > 0) {
+ struct stat Wtstat;
+ off_t residue = 0; /* input file size mod UTRSZ */
+ /*
+ * lastok will be the offset of the beginning of the most recent
+ * manifestly plausible and interesting input record in the current
+ * input file, if any.
+ * An invariant at loop entry is -UTRSZ <= lastok <= recin - UTRSZ.
+ */
+ off_t lastok = -(off_t)UTRSZ;
+ static off_t rectmp; /* current temp file position */
+ static boolean_t multimode = B_TRUE; /* multi-user RUN_LVL in force */
+ inrange_t is_ok; /* caches inrange() result */
+ /*
+ * During normal operation, records are of interest and copied to
+ * the output when is_ok >= INRANGE_PASS, ignored and dropped when
+ * is_ok == INRANGE_DROP, and evidence of corruption otherwise.
+ * While we are trying to recover from a corruption and hunting for
+ * records with sufficient redundancy to confirm that we have reached
+ * proper alignment again, we'll want is_ok >= INRANGE_ALIGNED.
+ * The value of want_ok is the minimum inrange() result of current
+ * interest. It is raised to INRANGE_ALIGNED during ongoing recovery
+ * and dropped back to INRANGE_PASS when we have recovered alignment.
+ */
+ inrange_t want_ok = INRANGE_PASS;
+ boolean_t recovered = B_FALSE; /* true after a successful recovery */
+ int n;
+
+ if (fstat(fileno(Wtmpx), &Wtstat) == -1) {
+ (void) fprintf(stderr,
+ "Cannot stat %s (will read sequentially): %s\n",
+ cur_input_name, strerror(errno));
+ } else if ((Wtstat.st_mode & S_IFMT) == S_IFREG) {
+ residue = Wtstat.st_size % UTRSZ;
+ }
+
+ /* if residue != 0, part of the file may be misaligned */
+ for (recin = 0;
+ ((n = fread(&Ut, (size_t)UTRSZ, (size_t)1, Wtmpx)) > 0) ||
+ (residue > 0);
+ recin += UTRSZ) {
if (n == 0) {
- unlink(Ofile);
- exit(0);
- }
- if (!inrange()) {
- for (;;) {
- if (fseek(Wtmpx,
- -(off_t)sizeof (Ut), 1) != 0) {
- perror("seek error\n");
- exit(1);
- }
- if ((ch = getc(Wtmpx)) == EOF) {
- perror("read\n");
- exit(1);
- }
- fprintf(stderr, "checking offset %lo\n",
- ftell(Wtmpx));
- if (fread(&Ut, sizeof (Ut), 1, Wtmpx) == 0) {
- exit(1);
- }
- if (inrange())
- break;
+ /*
+ * Implying residue > 0 and want_ok == INRANGE_PASS.
+ * It isn't worth telling an I/O error from EOF here.
+ * But one case is worth catching to avoid issuing a
+ * confusing message below. When the previous record
+ * had been ok, we just drop the current truncated
+ * record and bail out of the loop -- no seeking back.
+ */
+ if (lastok == recin - UTRSZ) {
+ wcomplain("file ends in mid-record, "
+ "final partial record dropped");
+ break;
+ } else {
+ wcomplain("file ends in mid-record");
+ /* handled below like a corrupted record */
+ is_ok = INRANGE_ERR;
}
+ } else
+ is_ok = inrange();
+
+ /* alignment recovery logic */
+ if ((residue > 0) && (is_ok == INRANGE_ERR)) {
+ /*
+ * "Let's go back to the last place where we knew
+ * where we were..."
+ * In fact, if the last record had been fine and we
+ * know there's at least one whole record ahead, we
+ * might move forward here (by residue bytes, less
+ * than one record's worth). In any case, we align
+ * ourselves to an integral number of records before
+ * the end of the file.
+ */
+ wcomplain("suspecting misaligned records, "
+ "repositioning");
+ recin = lastok + UTRSZ + residue;
+ residue = 0;
+ if (fseeko(Wtmpx, recin, SEEK_SET) != 0) {
+ (void) fprintf(stderr, "%s: seek: %s\n",
+ cur_input_name, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ wcomplain("starting re-scan");
+ /*
+ * While want_ok is elevated, only unequivocal records
+ * with inrange() == INRANGE_ALIGNED will be admitted
+ * to latch onto the tentative new alignment.
+ */
+ want_ok = INRANGE_ALIGNED;
+ /*
+ * Compensate for the loop continuation. Doing
+ * it this way gets the correct offset reported
+ * in the re-scan message above.
+ */
+ recin -= UTRSZ;
+ continue;
+ }
+ /* assert: residue == 0 or is_ok >= INRANGE_DROP here */
+ if (is_ok < want_ok)
+ /* record of no further interest */
+ continue;
+ if (want_ok == INRANGE_ALIGNED) {
+ wcomplain("now recognizing aligned records again");
+ want_ok = INRANGE_PASS;
+ recovered = B_TRUE;
}
- /* Now we have a good utmpx record, do more processing */
-
-#define UTYPE Ut.ut_type
-#define ULINE Ut.ut_line
-
- if (recno == 0 || UTYPE == BOOT_TIME)
- mkdtab(recno);
- if (UTYPE == RUN_LVL) {
- if (strncmp(ULINE, "run-level S", 11) == 0)
- multimode = 0;
- if (strncmp(ULINE, "run-level 2", 11) == 0)
- multimode++;
+ /*
+ * lastok must track recin whenever the current record is
+ * being processed and written out to our temp file, to avoid
+ * reprocessing any bits already done when we readjust our
+ * alignment.
+ */
+ lastok = recin;
+
+ /* now we have a good wtmpx record, do more processing */
+
+ if (rectmp == 0 || Ut.ut_type == BOOT_TIME)
+ mkdtab(rectmp);
+ if (Ut.ut_type == RUN_LVL) {
+ /* inrange() already checked the "run-level " part */
+ if (Ut.ut_line[RLVLMSG_LEN] == 'S')
+ multimode = B_FALSE;
+ else if ((Ut.ut_line[RLVLMSG_LEN] == '2') ||
+ (Ut.ut_line[RLVLMSG_LEN] == '3') ||
+ (Ut.ut_line[RLVLMSG_LEN] == '4'))
+ multimode = B_TRUE;
+ }
+ if (invalid(Ut.ut_name) == INVALID) {
+ (void) fprintf(stderr,
+ "wtmpfix: logname \"%*.*s\" changed "
+ "to \"INVALID\"\n", OUTPUT_NSZ,
+ OUTPUT_NSZ, Ut.ut_name);
+ (void) strncpy(Ut.ut_name, "INVALID", NSZ);
+ }
+ /*
+ * Special case: OLD_TIME should be immediately followed by
+ * NEW_TIME.
+ * We make no attempt at alignment recovery between these
+ * two: if there's junk at this point in the input, then
+ * a NEW_TIME seen after the junk probably won't be the one
+ * we are looking for.
+ */
+ if (Ut.ut_type == OLD_TIME) {
+ /*
+ * Make recin refer to the expected NEW_TIME.
+ * Loop continuation will increment it again
+ * for the record we're about to read now.
+ */
+ recin += UTRSZ;
+ if (!fread(&Ut2, (size_t)UTRSZ, (size_t)1, Wtmpx)) {
+ wcomplain("input truncated after OLD_TIME - "
+ "giving up");
+ exit(EXIT_FAILURE);
}
- if (invalid(Ut.ut_name)) {
- fprintf(stderr,
- "wtmpfix: logname \"%*.*s\" changed "
- "to \"INVALID\"\n", OUTPUT_NSZ,
- OUTPUT_NSZ, Ut.ut_name);
- (void) strncpy(Ut.ut_name, "INVALID", NSZ);
+ /*
+ * Rudimentary NEW_TIME sanity check. Not as thorough
+ * as in inrange(), but then we have redundancy from
+ * context here, since we're just after a plausible
+ * OLD_TIME record.
+ */
+ if ((Ut2.ut_type != NEW_TIME) ||
+ (strcmp(Ut2.ut_line, NTIME_MSG) != 0)) {
+ wcomplain("NEW_TIME expected but missing "
+ "after OLD_TIME - giving up");
+ exit(EXIT_FAILURE);
}
- if (UTYPE == OLD_TIME) {
- if (!winp(Wtmpx, &Ut2)) {
- fprintf(stderr, "Input truncated at "
- "offset %ld\n", recno);
- intr(0);
- }
- if (Ut2.ut_type != NEW_TIME) {
- fprintf(stderr, "New date expected at "
- "offset %ld", recno);
- intr(0);
- }
- if (multimode) /* multiuser */
- setdtab(recno, &Ut, &Ut2);
- recno += (2 * sizeof (struct utmpx));
- wout(Opw, &Ut);
- wout(Opw, &Ut2);
- continue;
+ lastok = recin;
+ if (multimode == B_TRUE)
+ setdtab(rectmp, &Ut, &Ut2);
+ rectmp += 2 * UTRSZ;
+ if ((fwrite(&Ut, UTRSZ, 1, Temp) < 1) ||
+ (fwrite(&Ut2, UTRSZ, 1, Temp) < 1)) {
+ perror("<temporary file>: fwrite");
+ exit(EXIT_FAILURE);
}
- wout(Opw, &Ut);
- recno += sizeof (struct utmpx);
+ continue;
+ }
+ if (fwrite(&Ut, UTRSZ, 1, Temp) < 1) {
+ perror("<temporary file>: fwrite");
+ exit(EXIT_FAILURE);
+ }
+ rectmp += UTRSZ;
+ }
+ if (want_ok == INRANGE_ALIGNED) {
+ wcomplain("EOF reached without recognizing another aligned "
+ "record with certainty. This file may need to be "
+ "repaired by hand.\n");
+ } else if (recovered == B_TRUE) {
+ /*
+ * There may have been a number of wcomplain() messages
+ * since we reported about the re-scan, so it bears repeating
+ * at the end that not all was well.
+ */
+ wcomplain("EOF reached after recovering from corruption "
+ "in the middle of the file. This file may need to be "
+ "repaired by hand.\n");
}
}
-static int
+/*
+ * inrange: inspect what we hope to be one wtmpx record.
+ * Globals: Ut, lastmonth, nextmonth; recin, cur_input_name (diagnostics)
+ * Return values:
+ * INRANGE_ERR -- an inconsistency was detected, input file corrupted
+ * INRANGE_DROP -- Ut appears consistent but isn't of interest
+ * (of process type and outside the time range we want)
+ * INRANGE_PASS -- Ut appears consistent and this record is of interest
+ * INRANGE_ALIGNED -- same, and it is also redundant enough to be sure
+ * that we're correctly aligned on record boundaries
+ */
+#define UNEXPECTED_UT_PID \
+ (Ut.ut_pid != 0) || \
+ (Ut.ut_exit.e_termination != 0) || \
+ (Ut.ut_exit.e_exit != 0)
+
+static inrange_t
inrange()
{
- if ((strcmp(Ut.ut_line, RUNLVL_MSG) == 0) ||
- (strcmp(Ut.ut_line, BOOT_MSG) == 0) ||
- (strcmp(Ut.ut_line, "acctg on") == 0) ||
- (strcmp(Ut.ut_line, OTIME_MSG) == 0) ||
- (strcmp(Ut.ut_line, NTIME_MSG) == 0))
- return (1);
-
- if (Ut.ut_id != 0 &&
- Ut.ut_xtime > 0 &&
- Ut.ut_xtime > lastmonth &&
- Ut.ut_xtime < nextmonth &&
- Ut.ut_type >= EMPTY &&
- Ut.ut_type <= UTMAXTYPE &&
- Ut.ut_pid >= 0)
- return (1);
+ /* pid_t is signed so that fork() can return -1. Exploit this. */
+ if (Ut.ut_pid < 0) {
+ wcomplain("negative pid");
+ return (INRANGE_ERR);
+ }
- return (0);
+ /* the legal values for ut_type are enumerated in <utmp.h> */
+ switch (Ut.ut_type) {
+ case EMPTY:
+ if (UNEXPECTED_UT_PID) {
+ wcomplain("nonzero pid or status in EMPTY record");
+ return (INRANGE_ERR);
+ }
+ /*
+ * We'd like to have Ut.ut_user[0] == '\0' here, but sadly
+ * this isn't always so, so we can't rely on it.
+ */
+ return (INRANGE_DROP);
+ case RUN_LVL:
+ /* ut_line must have come from the RUNLVL_MSG pattern */
+ if (strncmp(Ut.ut_line, RUN_LEVEL_MSG, RLVLMSG_LEN) != 0) {
+ wcomplain("RUN_LVL record doesn't say `"
+ RUN_LEVEL_MSG "'");
+ return (INRANGE_ERR);
+ }
+ /*
+ * The ut_pid, termination, and exit status fields have
+ * special meaning in this case, and none of them is
+ * suitable for checking. And we won't insist on ut_user
+ * to always be an empty string.
+ */
+ return (INRANGE_ALIGNED);
+ case BOOT_TIME:
+ if (UNEXPECTED_UT_PID) {
+ wcomplain("nonzero pid or status in BOOT_TIME record");
+ return (INRANGE_ERR);
+ }
+ if (strcmp(Ut.ut_line, BOOT_MSG) != 0) {
+ wcomplain("BOOT_TIME record doesn't say `"
+ BOOT_MSG "'");
+ return (INRANGE_ERR);
+ }
+ return (INRANGE_ALIGNED);
+ case OLD_TIME:
+ if (UNEXPECTED_UT_PID) {
+ wcomplain("nonzero pid or status in OLD_TIME record");
+ return (INRANGE_ERR);
+ }
+ if (strcmp(Ut.ut_line, OTIME_MSG) != 0) {
+ wcomplain("OLD_TIME record doesn't say `"
+ OTIME_MSG "'");
+ return (INRANGE_ERR);
+ }
+ return (INRANGE_ALIGNED);
+ case NEW_TIME:
+ /*
+ * We don't actually expect to see any here. If they follow
+ * an OLD_TIME record as they should, they'll be handled on
+ * the fly in scanfile(). But we might still run into one
+ * if the input is somehow corrupted.
+ */
+ if (UNEXPECTED_UT_PID) {
+ wcomplain("nonzero pid or status in NEW_TIME record");
+ return (INRANGE_ERR);
+ }
+ if (strcmp(Ut.ut_line, NTIME_MSG) != 0) {
+ wcomplain("NEW_TIME record doesn't say `"
+ NTIME_MSG "'");
+ return (INRANGE_ERR);
+ }
+ return (INRANGE_ALIGNED);
+
+ /* the four *_PROCESS ut_types have a lot in common */
+ case USER_PROCESS:
+ /*
+ * Catch two special cases first: psradm records have no id
+ * and no pid, while root login over FTP may not have a
+ * valid ut_user and may have garbage in ut_id[3].
+ */
+ if ((strcmp(Ut.ut_user, "psradm") == 0) &&
+ (Ut.ut_id[0] == '\0') &&
+ (Ut.ut_pid > 0)) {
+ if ((Ut.ut_xtime > lastmonth) &&
+ (Ut.ut_xtime < nextmonth)) {
+ return (INRANGE_ALIGNED);
+ } else {
+ return (INRANGE_DROP);
+ }
+ }
+ if ((Ut.ut_user[0] == '\0') &&
+ (strncmp(Ut.ut_id, "ftp", 3) == 0) &&
+ (strncmp(Ut.ut_line, "ftp", 3) == 0)) {
+ if ((Ut.ut_xtime > lastmonth) &&
+ (Ut.ut_xtime < nextmonth)) {
+ return (INRANGE_ALIGNED);
+ } else {
+ return (INRANGE_DROP);
+ }
+ }
+ /* FALLTHROUGH */
+ case LOGIN_PROCESS:
+ if (Ut.ut_user[0] == '\0') {
+ wcomplain("missing username in process record");
+ return (INRANGE_ERR);
+ }
+ /* FALLTHROUGH */
+ case INIT_PROCESS:
+ /*
+ * INIT_PROCESS and DEAD_PROCESS records can come with an
+ * empty ut_user in degenerate cases (e.g. syntax errors
+ * like a comment-only process field in /etc/inittab).
+ * But in an INIT_PROCESS, LOGIN_PROCESS, or USER_PROCESS
+ * record, we expect a respectable ut_pid.
+ */
+ if (Ut.ut_pid == 0) {
+ wcomplain("null pid in process record");
+ return (INRANGE_ERR);
+ }
+ /* FALLTHROUGH */
+ case DEAD_PROCESS:
+ /*
+ * DEAD_PROCESS records with a null ut_pid can be produced
+ * by gnome-terminal (normally seen in utmpx only, but they
+ * can leak into wtmpx in rare circumstances).
+ * Unfortunately, ut_id can't be relied on to contain
+ * anything in particular. (E.g., sshd might leave it
+ * 0-initialized.) This leaves almost no verifiable
+ * redundancy here beyond the ut_type.
+ * At least we insist on a reasonable timestamp.
+ */
+ if (Ut.ut_xtime <= 0) {
+ wcomplain("non-positive time in process record");
+ return (INRANGE_ERR);
+ }
+ if ((Ut.ut_xtime > lastmonth) &&
+ (Ut.ut_xtime < nextmonth)) {
+ return (INRANGE_PASS);
+ } else {
+ return (INRANGE_DROP);
+ }
+ case ACCOUNTING:
+ /*
+ * If we recognize one of the three reason strings passed
+ * by the /usr/lib/acct shell scripts to acctwtmp, we
+ * exploit the available redundancy they offer. But
+ * acctwtmp could have been invoked by custom scripts or
+ * interactively with other reason strings in the first
+ * argument, so anything we don't recognize does not
+ * constitute evidence for corruption.
+ */
+ if ((strcmp(Ut.ut_line, RUNACCT_MSG) != 0) &&
+ (strcmp(Ut.ut_line, ACCTG_ON_MSG) != 0) &&
+ (strcmp(Ut.ut_line, ACCTG_OFF_MSG) != 0)) {
+ return (INRANGE_DROP);
+ }
+ return (INRANGE_ALIGNED);
+ case DOWN_TIME:
+ if (UNEXPECTED_UT_PID) {
+ wcomplain("nonzero pid or status in DOWN_TIME record");
+ return (INRANGE_ERR);
+ }
+ if (strcmp(Ut.ut_line, DOWN_MSG) != 0) {
+ wcomplain("DOWN_TIME record doesn't say `"
+ DOWN_MSG "'");
+ return (INRANGE_ERR);
+ }
+ return (INRANGE_ALIGNED);
+ default:
+ wcomplain("ut_type out of range");
+ return (INRANGE_ERR);
+ }
+ /* NOTREACHED */
}
static void
-wabort(int sig)
+wcomplain(char *msg)
{
- fprintf(stderr, "give up\n");
- exit(1);
+ (void) fprintf(stderr, "%s: offset %lld: %s\n", cur_input_name,
+ (longlong_t)recin, msg);
}