diff options
Diffstat (limited to 'usr/src')
-rw-r--r-- | usr/src/cmd/acct/wtmpfix.c | 717 |
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); } |