17c478bd9Sstevel@tonic-gate /*
27c478bd9Sstevel@tonic-gate * CDDL HEADER START
37c478bd9Sstevel@tonic-gate *
47c478bd9Sstevel@tonic-gate * The contents of this file are subject to the terms of the
5dd371263Srm * Common Development and Distribution License (the "License").
6dd371263Srm * You may not use this file except in compliance with the License.
77c478bd9Sstevel@tonic-gate *
87c478bd9Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
97c478bd9Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing.
107c478bd9Sstevel@tonic-gate * See the License for the specific language governing permissions
117c478bd9Sstevel@tonic-gate * and limitations under the License.
127c478bd9Sstevel@tonic-gate *
137c478bd9Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each
147c478bd9Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
157c478bd9Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the
167c478bd9Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying
177c478bd9Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner]
187c478bd9Sstevel@tonic-gate *
197c478bd9Sstevel@tonic-gate * CDDL HEADER END
207c478bd9Sstevel@tonic-gate */
217c478bd9Sstevel@tonic-gate
227c478bd9Sstevel@tonic-gate /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
237c478bd9Sstevel@tonic-gate /* All Rights Reserved */
247c478bd9Sstevel@tonic-gate
25414388d7Ssl /*
26dd371263Srm * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
27414388d7Ssl * Use is subject to license terms.
28414388d7Ssl */
297c478bd9Sstevel@tonic-gate
307c478bd9Sstevel@tonic-gate /*
317c478bd9Sstevel@tonic-gate * wtmpfix - adjust wtmpx file and remove date changes.
327c478bd9Sstevel@tonic-gate * wtmpfix <wtmpx1 >wtmpx2
337c478bd9Sstevel@tonic-gate *
34dd371263Srm * Can recover to some extent from wtmpx corruption.
357c478bd9Sstevel@tonic-gate */
367c478bd9Sstevel@tonic-gate
377c478bd9Sstevel@tonic-gate #include <stdio.h>
387c478bd9Sstevel@tonic-gate #include <sys/types.h>
39dd371263Srm #include <sys/stat.h>
407c478bd9Sstevel@tonic-gate #include <sys/param.h>
417c478bd9Sstevel@tonic-gate #include "acctdef.h"
427c478bd9Sstevel@tonic-gate #include <utmpx.h>
437c478bd9Sstevel@tonic-gate #include <time.h>
447c478bd9Sstevel@tonic-gate #include <ctype.h>
457c478bd9Sstevel@tonic-gate #include <locale.h>
467c478bd9Sstevel@tonic-gate #include <stdlib.h>
47dd371263Srm #include <string.h>
48dd371263Srm #include <errno.h>
497c478bd9Sstevel@tonic-gate
507c478bd9Sstevel@tonic-gate #define DAYEPOCH (60 * 60 * 24)
51dd371263Srm #define UTRSZ (sizeof (struct futmpx)) /* file record size */
52dd371263Srm
53dd371263Srm /*
54*bbf21555SRichard Lowe * The acctsh(8) shell scripts startup(8) and shutacct(8) as well as the
55dd371263Srm * runacct script each pass their own specific reason strings in the first
56*bbf21555SRichard Lowe * argument to acctwtmp(8), to be propagated into ut_line fields. Additional
57dd371263Srm * reasons (RUNLVL_MSG, ..., DOWN_MSG), used by compiled code, are defined in
58dd371263Srm * <utmp.h> as preprocessor constants.
59dd371263Srm * For simplicity we predefine similar constants for the scripted strings
60dd371263Srm * here, as no other compiled code uses those.
61dd371263Srm * Moreover, we need a variant of RUNLVL_MSG without the "%c" at the end.
62dd371263Srm * We shall use the fact that ut_line[RLVLMSG_LEN] will extract the char
63dd371263Srm * in the %c position ('S', '2', ...).
64dd371263Srm * Since all of these string constants are '\0' terminated, they can safely
65dd371263Srm * be used with strcmp() even when ut_line is not.
66dd371263Srm */
67dd371263Srm #define RUN_LEVEL_MSG "run-level "
68dd371263Srm #define ACCTG_ON_MSG "acctg on"
69dd371263Srm #define ACCTG_OFF_MSG "acctg off"
70dd371263Srm #define RUNACCT_MSG "runacct"
71dd371263Srm
72dd371263Srm #define RLVLMSG_LEN (sizeof (RUN_LEVEL_MSG) - 1)
73dd371263Srm
74dd371263Srm /*
75dd371263Srm * Records encountered are classified as one of the following: corrupted;
76dd371263Srm * ok but devoid of interest to acctcon downstream; ok and interesting;
77dd371263Srm * or ok and even redundant enough to latch onto a new alignment whilst
78dd371263Srm * recovering from a corruption.
79dd371263Srm * The ordering among these four symbolic values is significant.
80dd371263Srm */
81dd371263Srm typedef enum {
82dd371263Srm INRANGE_ERR = -1,
83dd371263Srm INRANGE_DROP,
84dd371263Srm INRANGE_PASS,
85dd371263Srm INRANGE_ALIGNED
86dd371263Srm } inrange_t;
87dd371263Srm
88dd371263Srm /* input filenames and record numbers, for diagnostics only */
89dd371263Srm #define STDIN_NAME "<stdin>"
90dd371263Srm static char *cur_input_name;
91dd371263Srm static off_t recin;
927c478bd9Sstevel@tonic-gate
93dd371263Srm static FILE *Wtmpx, *Temp;
947c478bd9Sstevel@tonic-gate
957c478bd9Sstevel@tonic-gate struct dtab
967c478bd9Sstevel@tonic-gate {
977c478bd9Sstevel@tonic-gate off_t d_off1; /* file offset start */
987c478bd9Sstevel@tonic-gate off_t d_off2; /* file offset stop */
997c478bd9Sstevel@tonic-gate time_t d_adj; /* time adjustment */
1007c478bd9Sstevel@tonic-gate struct dtab *d_ndp; /* next record */
1017c478bd9Sstevel@tonic-gate };
1027c478bd9Sstevel@tonic-gate
103dd371263Srm static struct dtab *Fdp; /* list header */
104dd371263Srm static struct dtab *Ldp; /* list trailer */
1057c478bd9Sstevel@tonic-gate
106dd371263Srm static time_t lastmonth, nextmonth;
1077c478bd9Sstevel@tonic-gate
108dd371263Srm static struct futmpx Ut, Ut2;
1097c478bd9Sstevel@tonic-gate
110dd371263Srm static int winp(FILE *, struct futmpx *);
1117c478bd9Sstevel@tonic-gate static void mkdtab(off_t);
112dd371263Srm static void setdtab(off_t, struct futmpx *, struct futmpx *);
113dd371263Srm static void adjust(off_t, struct futmpx *);
1147c478bd9Sstevel@tonic-gate static int invalid(char *);
1157c478bd9Sstevel@tonic-gate static void scanfile(void);
116dd371263Srm static inrange_t inrange(void);
117dd371263Srm static void wcomplain(char *);
1187c478bd9Sstevel@tonic-gate
1197c478bd9Sstevel@tonic-gate int
main(int argc,char ** argv)1207c478bd9Sstevel@tonic-gate main(int argc, char **argv)
1217c478bd9Sstevel@tonic-gate {
1227c478bd9Sstevel@tonic-gate time_t tloc;
1237c478bd9Sstevel@tonic-gate struct tm *tmp;
124dd371263Srm int year;
125dd371263Srm int month;
126dd371263Srm off_t rectmpin;
1277c478bd9Sstevel@tonic-gate
1287c478bd9Sstevel@tonic-gate (void) setlocale(LC_ALL, "");
1297c478bd9Sstevel@tonic-gate setbuf(stdout, NULL);
1307c478bd9Sstevel@tonic-gate
131dd371263Srm (void) time(&tloc);
1327c478bd9Sstevel@tonic-gate tmp = localtime(&tloc);
1337c478bd9Sstevel@tonic-gate year = tmp->tm_year;
1347c478bd9Sstevel@tonic-gate month = tmp->tm_mon + 1;
1357c478bd9Sstevel@tonic-gate lastmonth = ((year + 1900 - 1970) * 365 +
1367c478bd9Sstevel@tonic-gate (month - 1) * 30) * DAYEPOCH;
1377c478bd9Sstevel@tonic-gate nextmonth = ((year + 1900 - 1970) * 365 +
1387c478bd9Sstevel@tonic-gate (month + 1) * 30) * DAYEPOCH;
1397c478bd9Sstevel@tonic-gate
1407c478bd9Sstevel@tonic-gate if (argc < 2) {
1417c478bd9Sstevel@tonic-gate argv[argc] = "-";
1427c478bd9Sstevel@tonic-gate argc++;
1437c478bd9Sstevel@tonic-gate }
1447c478bd9Sstevel@tonic-gate
145dd371263Srm /*
146dd371263Srm * Almost all system call failures in this program are unrecoverable
147dd371263Srm * and therefore fatal. Typical causes might be lack of memory or
148dd371263Srm * of space in a filesystem. If necessary, the system administrator
149dd371263Srm * can invoke /usr/lib/acct/runacct interactively after making room
150dd371263Srm * to complete the remaining phases of last night's accounting.
151dd371263Srm */
152dd371263Srm if ((Temp = tmpfile()) == NULL) {
153dd371263Srm perror("Cannot create temporary file");
154dd371263Srm return (EXIT_FAILURE);
1557c478bd9Sstevel@tonic-gate }
1567c478bd9Sstevel@tonic-gate
1577c478bd9Sstevel@tonic-gate while (--argc > 0) {
1587c478bd9Sstevel@tonic-gate argv++;
159dd371263Srm if (strcmp(*argv, "-") == 0) {
1607c478bd9Sstevel@tonic-gate Wtmpx = stdin;
161dd371263Srm cur_input_name = STDIN_NAME;
162dd371263Srm } else if ((Wtmpx = fopen(*argv, "r")) == NULL) {
163dd371263Srm (void) fprintf(stderr, "Cannot open %s: %s\n",
164dd371263Srm *argv, strerror(errno));
165dd371263Srm return (EXIT_FAILURE);
166dd371263Srm } else {
167dd371263Srm cur_input_name = *argv;
1687c478bd9Sstevel@tonic-gate }
169dd371263Srm /*
170dd371263Srm * Filter records reading from current input stream Wtmpx,
171dd371263Srm * writing to Temp.
172dd371263Srm */
1737c478bd9Sstevel@tonic-gate scanfile();
1747c478bd9Sstevel@tonic-gate
1757c478bd9Sstevel@tonic-gate if (Wtmpx != stdin)
176dd371263Srm (void) fclose(Wtmpx);
1777c478bd9Sstevel@tonic-gate }
178dd371263Srm /* flush and rewind Temp for readback */
179dd371263Srm if (fflush(Temp) != 0) {
180dd371263Srm perror("<temporary file>: fflush");
181dd371263Srm return (EXIT_FAILURE);
182dd371263Srm }
183dd371263Srm if (fseeko(Temp, (off_t)0L, SEEK_SET) != 0) {
184dd371263Srm perror("<temporary file>: seek");
185dd371263Srm return (EXIT_FAILURE);
186dd371263Srm }
187dd371263Srm /* second pass: apply time adjustments */
188dd371263Srm rectmpin = 0;
189dd371263Srm while (winp(Temp, &Ut)) {
190dd371263Srm adjust(rectmpin, &Ut);
191dd371263Srm rectmpin += UTRSZ;
192dd371263Srm if (fwrite(&Ut, UTRSZ, 1, stdout) < 1) {
193dd371263Srm perror("<stdout>: fwrite");
194dd371263Srm return (EXIT_FAILURE);
195dd371263Srm }
1967c478bd9Sstevel@tonic-gate }
197dd371263Srm (void) fclose(Temp);
198dd371263Srm /*
199dd371263Srm * Detect if we've run out of space (say) and exit unsuccessfully
200dd371263Srm * so that downstream accounting utilities won't start processing an
201dd371263Srm * incomplete tmpwtmp file.
202dd371263Srm */
203dd371263Srm if (fflush(stdout) != 0) {
204dd371263Srm perror("<stdout>: fflush");
205dd371263Srm return (EXIT_FAILURE);
2067c478bd9Sstevel@tonic-gate }
207dd371263Srm return (EXIT_SUCCESS);
2087c478bd9Sstevel@tonic-gate }
2097c478bd9Sstevel@tonic-gate
2107c478bd9Sstevel@tonic-gate static int
winp(FILE * f,struct futmpx * w)211dd371263Srm winp(FILE *f, struct futmpx *w)
2127c478bd9Sstevel@tonic-gate {
213dd371263Srm if (fread(w, (size_t)UTRSZ, (size_t)1, f) != 1)
2147c478bd9Sstevel@tonic-gate return (0);
2157c478bd9Sstevel@tonic-gate if ((w->ut_type >= EMPTY) && (w->ut_type <= UTMAXTYPE))
2167c478bd9Sstevel@tonic-gate return (1);
2177c478bd9Sstevel@tonic-gate else {
218dd371263Srm (void) fprintf(stderr, "Bad temp file at offset %lld\n",
219dd371263Srm (longlong_t)(ftell(f) - UTRSZ));
220dd371263Srm /*
221dd371263Srm * If input was corrupt, neither ut_line nor ut_user can be
222dd371263Srm * relied on to be \0-terminated. Even fixing the precision
223dd371263Srm * does not entirely guard against this.
224dd371263Srm */
225dd371263Srm (void) fprintf(stderr,
226dd371263Srm "ut_line \"%-12.12s\" ut_user \"%-8.8s\" ut_xtime %ld\n",
227dd371263Srm w->ut_line, w->ut_user, (long)w->ut_xtime);
228dd371263Srm exit(EXIT_FAILURE);
2297c478bd9Sstevel@tonic-gate }
2307c478bd9Sstevel@tonic-gate /* NOTREACHED */
2317c478bd9Sstevel@tonic-gate }
2327c478bd9Sstevel@tonic-gate
2337c478bd9Sstevel@tonic-gate static void
mkdtab(off_t p)2347c478bd9Sstevel@tonic-gate mkdtab(off_t p)
2357c478bd9Sstevel@tonic-gate {
2367c478bd9Sstevel@tonic-gate
2377c478bd9Sstevel@tonic-gate struct dtab *dp;
2387c478bd9Sstevel@tonic-gate
2397c478bd9Sstevel@tonic-gate dp = Ldp;
2407c478bd9Sstevel@tonic-gate if (dp == NULL) {
2417c478bd9Sstevel@tonic-gate dp = calloc(sizeof (struct dtab), 1);
2427c478bd9Sstevel@tonic-gate if (dp == NULL) {
243dd371263Srm (void) fprintf(stderr, "out of memory\n");
244dd371263Srm exit(EXIT_FAILURE);
2457c478bd9Sstevel@tonic-gate }
2467c478bd9Sstevel@tonic-gate Fdp = Ldp = dp;
2477c478bd9Sstevel@tonic-gate }
2487c478bd9Sstevel@tonic-gate dp->d_off1 = p;
2497c478bd9Sstevel@tonic-gate }
2507c478bd9Sstevel@tonic-gate
2517c478bd9Sstevel@tonic-gate static void
setdtab(off_t p,struct futmpx * w1,struct futmpx * w2)252dd371263Srm setdtab(off_t p, struct futmpx *w1, struct futmpx *w2)
2537c478bd9Sstevel@tonic-gate {
2547c478bd9Sstevel@tonic-gate struct dtab *dp;
2557c478bd9Sstevel@tonic-gate
2567c478bd9Sstevel@tonic-gate if ((dp = Ldp) == NULL) {
257dd371263Srm (void) fprintf(stderr, "no dtab\n");
258dd371263Srm exit(EXIT_FAILURE);
2597c478bd9Sstevel@tonic-gate }
2607c478bd9Sstevel@tonic-gate dp->d_off2 = p;
2617c478bd9Sstevel@tonic-gate dp->d_adj = w2->ut_xtime - w1->ut_xtime;
2627c478bd9Sstevel@tonic-gate if ((Ldp = calloc(sizeof (struct dtab), 1)) == NULL) {
263dd371263Srm (void) fprintf(stderr, "out of memory\n");
264dd371263Srm exit(EXIT_FAILURE);
2657c478bd9Sstevel@tonic-gate }
2667c478bd9Sstevel@tonic-gate Ldp->d_off1 = dp->d_off1;
2677c478bd9Sstevel@tonic-gate dp->d_ndp = Ldp;
2687c478bd9Sstevel@tonic-gate }
2697c478bd9Sstevel@tonic-gate
2707c478bd9Sstevel@tonic-gate static void
adjust(off_t p,struct futmpx * w)271dd371263Srm adjust(off_t p, struct futmpx *w)
2727c478bd9Sstevel@tonic-gate {
2737c478bd9Sstevel@tonic-gate
2747c478bd9Sstevel@tonic-gate off_t pp;
2757c478bd9Sstevel@tonic-gate struct dtab *dp;
2767c478bd9Sstevel@tonic-gate
2777c478bd9Sstevel@tonic-gate pp = p;
2787c478bd9Sstevel@tonic-gate
2797c478bd9Sstevel@tonic-gate for (dp = Fdp; dp != NULL; dp = dp->d_ndp) {
2807c478bd9Sstevel@tonic-gate if (dp->d_adj == 0)
2817c478bd9Sstevel@tonic-gate continue;
282dd371263Srm if (pp >= dp->d_off1 && pp <= dp->d_off2)
2837c478bd9Sstevel@tonic-gate w->ut_xtime += dp->d_adj;
2847c478bd9Sstevel@tonic-gate }
2857c478bd9Sstevel@tonic-gate }
2867c478bd9Sstevel@tonic-gate
2877c478bd9Sstevel@tonic-gate /*
288dd371263Srm * invalid() determines whether the name field adheres to the criteria
289dd371263Srm * set forth in acctcon1. If returns VALID if the name is ok, or
290dd371263Srm * INVALID if the name violates conventions.
2917c478bd9Sstevel@tonic-gate */
2927c478bd9Sstevel@tonic-gate
2937c478bd9Sstevel@tonic-gate static int
invalid(char * name)2947c478bd9Sstevel@tonic-gate invalid(char *name)
2957c478bd9Sstevel@tonic-gate {
2967c478bd9Sstevel@tonic-gate int i;
2977c478bd9Sstevel@tonic-gate
2987c478bd9Sstevel@tonic-gate for (i = 0; i < NSZ; i++) {
2997c478bd9Sstevel@tonic-gate if (name[i] == '\0')
3007c478bd9Sstevel@tonic-gate return (VALID);
3017c478bd9Sstevel@tonic-gate if (! (isalnum(name[i]) || (name[i] == '$') ||
3027c478bd9Sstevel@tonic-gate (name[i] == ' ') || (name[i] == '.') ||
3037c478bd9Sstevel@tonic-gate (name[i] == '_') || (name[i] == '-'))) {
3047c478bd9Sstevel@tonic-gate return (INVALID);
3057c478bd9Sstevel@tonic-gate }
3067c478bd9Sstevel@tonic-gate }
3077c478bd9Sstevel@tonic-gate return (VALID);
3087c478bd9Sstevel@tonic-gate }
3097c478bd9Sstevel@tonic-gate
3107c478bd9Sstevel@tonic-gate /*
3117c478bd9Sstevel@tonic-gate * scanfile:
312dd371263Srm * 1) reads the current input file
313dd371263Srm * 2) filters for process records in time range of interest and for
314dd371263Srm * other types of records deemed interesting to acctcon downstream
315dd371263Srm * 3) picks up time changes with setdtab() if in multiuser mode, which
316dd371263Srm * will be applied when the temp file is read back
317dd371263Srm * 4) changes bad login names to INVALID
318dd371263Srm * 5) recovers from common cases of wtmpx corruption (loss of record
319dd371263Srm * alignment).
320dd371263Srm * All of the static globals are used directly or indirectly.
321dd371263Srm *
322dd371263Srm * When wtmpfix is asked to process several input files in succession,
323dd371263Srm * some state needs to be preserved from one scanfile() invocation to the
324dd371263Srm * next. Aside from the temp file position, we remember whether we were
325dd371263Srm * in multi-user mode or not. Absent evidence to the contrary, we begin
326dd371263Srm * processing assuming multi-user mode, because runacct's wtmpx rotation
327*bbf21555SRichard Lowe * normally gives us a file recently initialized by utmp2wtmp(8) with no
328dd371263Srm * older RUN_LVL records surviving.
3297c478bd9Sstevel@tonic-gate */
3307c478bd9Sstevel@tonic-gate
3317c478bd9Sstevel@tonic-gate static void
scanfile()3327c478bd9Sstevel@tonic-gate scanfile()
3337c478bd9Sstevel@tonic-gate {
334dd371263Srm struct stat Wtstat;
335dd371263Srm off_t residue = 0; /* input file size mod UTRSZ */
336dd371263Srm /*
337dd371263Srm * lastok will be the offset of the beginning of the most recent
338dd371263Srm * manifestly plausible and interesting input record in the current
339dd371263Srm * input file, if any.
340dd371263Srm * An invariant at loop entry is -UTRSZ <= lastok <= recin - UTRSZ.
341dd371263Srm */
342dd371263Srm off_t lastok = -(off_t)UTRSZ;
343dd371263Srm static off_t rectmp; /* current temp file position */
344dd371263Srm static boolean_t multimode = B_TRUE; /* multi-user RUN_LVL in force */
345dd371263Srm inrange_t is_ok; /* caches inrange() result */
346dd371263Srm /*
347dd371263Srm * During normal operation, records are of interest and copied to
348dd371263Srm * the output when is_ok >= INRANGE_PASS, ignored and dropped when
349dd371263Srm * is_ok == INRANGE_DROP, and evidence of corruption otherwise.
350dd371263Srm * While we are trying to recover from a corruption and hunting for
351dd371263Srm * records with sufficient redundancy to confirm that we have reached
352dd371263Srm * proper alignment again, we'll want is_ok >= INRANGE_ALIGNED.
353dd371263Srm * The value of want_ok is the minimum inrange() result of current
354dd371263Srm * interest. It is raised to INRANGE_ALIGNED during ongoing recovery
355dd371263Srm * and dropped back to INRANGE_PASS when we have recovered alignment.
356dd371263Srm */
357dd371263Srm inrange_t want_ok = INRANGE_PASS;
358dd371263Srm boolean_t recovered = B_FALSE; /* true after a successful recovery */
359dd371263Srm int n;
360dd371263Srm
361dd371263Srm if (fstat(fileno(Wtmpx), &Wtstat) == -1) {
362dd371263Srm (void) fprintf(stderr,
363dd371263Srm "Cannot stat %s (will read sequentially): %s\n",
364dd371263Srm cur_input_name, strerror(errno));
365dd371263Srm } else if ((Wtstat.st_mode & S_IFMT) == S_IFREG) {
366dd371263Srm residue = Wtstat.st_size % UTRSZ;
367dd371263Srm }
368dd371263Srm
369dd371263Srm /* if residue != 0, part of the file may be misaligned */
370dd371263Srm for (recin = 0;
371dd371263Srm ((n = fread(&Ut, (size_t)UTRSZ, (size_t)1, Wtmpx)) > 0) ||
372dd371263Srm (residue > 0);
373dd371263Srm recin += UTRSZ) {
3747c478bd9Sstevel@tonic-gate if (n == 0) {
375dd371263Srm /*
376dd371263Srm * Implying residue > 0 and want_ok == INRANGE_PASS.
377dd371263Srm * It isn't worth telling an I/O error from EOF here.
378dd371263Srm * But one case is worth catching to avoid issuing a
379dd371263Srm * confusing message below. When the previous record
380dd371263Srm * had been ok, we just drop the current truncated
381dd371263Srm * record and bail out of the loop -- no seeking back.
382dd371263Srm */
383dd371263Srm if (lastok == recin - UTRSZ) {
384dd371263Srm wcomplain("file ends in mid-record, "
385dd371263Srm "final partial record dropped");
386dd371263Srm break;
387dd371263Srm } else {
388dd371263Srm wcomplain("file ends in mid-record");
389dd371263Srm /* handled below like a corrupted record */
390dd371263Srm is_ok = INRANGE_ERR;
3917c478bd9Sstevel@tonic-gate }
392dd371263Srm } else
393dd371263Srm is_ok = inrange();
394dd371263Srm
395dd371263Srm /* alignment recovery logic */
396dd371263Srm if ((residue > 0) && (is_ok == INRANGE_ERR)) {
397dd371263Srm /*
398dd371263Srm * "Let's go back to the last place where we knew
399dd371263Srm * where we were..."
400dd371263Srm * In fact, if the last record had been fine and we
401dd371263Srm * know there's at least one whole record ahead, we
402dd371263Srm * might move forward here (by residue bytes, less
403dd371263Srm * than one record's worth). In any case, we align
404dd371263Srm * ourselves to an integral number of records before
405dd371263Srm * the end of the file.
406dd371263Srm */
407dd371263Srm wcomplain("suspecting misaligned records, "
408dd371263Srm "repositioning");
409dd371263Srm recin = lastok + UTRSZ + residue;
410dd371263Srm residue = 0;
411dd371263Srm if (fseeko(Wtmpx, recin, SEEK_SET) != 0) {
412dd371263Srm (void) fprintf(stderr, "%s: seek: %s\n",
413dd371263Srm cur_input_name, strerror(errno));
414dd371263Srm exit(EXIT_FAILURE);
415dd371263Srm }
416dd371263Srm wcomplain("starting re-scan");
417dd371263Srm /*
418dd371263Srm * While want_ok is elevated, only unequivocal records
419dd371263Srm * with inrange() == INRANGE_ALIGNED will be admitted
420dd371263Srm * to latch onto the tentative new alignment.
421dd371263Srm */
422dd371263Srm want_ok = INRANGE_ALIGNED;
423dd371263Srm /*
424dd371263Srm * Compensate for the loop continuation. Doing
425dd371263Srm * it this way gets the correct offset reported
426dd371263Srm * in the re-scan message above.
427dd371263Srm */
428dd371263Srm recin -= UTRSZ;
429dd371263Srm continue;
430dd371263Srm }
431dd371263Srm /* assert: residue == 0 or is_ok >= INRANGE_DROP here */
432dd371263Srm if (is_ok < want_ok)
433dd371263Srm /* record of no further interest */
434dd371263Srm continue;
435dd371263Srm if (want_ok == INRANGE_ALIGNED) {
436dd371263Srm wcomplain("now recognizing aligned records again");
437dd371263Srm want_ok = INRANGE_PASS;
438dd371263Srm recovered = B_TRUE;
4397c478bd9Sstevel@tonic-gate }
440dd371263Srm /*
441dd371263Srm * lastok must track recin whenever the current record is
442dd371263Srm * being processed and written out to our temp file, to avoid
443dd371263Srm * reprocessing any bits already done when we readjust our
444dd371263Srm * alignment.
445dd371263Srm */
446dd371263Srm lastok = recin;
447dd371263Srm
448dd371263Srm /* now we have a good wtmpx record, do more processing */
449dd371263Srm
450dd371263Srm if (rectmp == 0 || Ut.ut_type == BOOT_TIME)
451dd371263Srm mkdtab(rectmp);
452dd371263Srm if (Ut.ut_type == RUN_LVL) {
453dd371263Srm /* inrange() already checked the "run-level " part */
454dd371263Srm if (Ut.ut_line[RLVLMSG_LEN] == 'S')
455dd371263Srm multimode = B_FALSE;
456dd371263Srm else if ((Ut.ut_line[RLVLMSG_LEN] == '2') ||
457dd371263Srm (Ut.ut_line[RLVLMSG_LEN] == '3') ||
458dd371263Srm (Ut.ut_line[RLVLMSG_LEN] == '4'))
459dd371263Srm multimode = B_TRUE;
460dd371263Srm }
461dd371263Srm if (invalid(Ut.ut_name) == INVALID) {
462dd371263Srm (void) fprintf(stderr,
463dd371263Srm "wtmpfix: logname \"%*.*s\" changed "
464dd371263Srm "to \"INVALID\"\n", OUTPUT_NSZ,
465dd371263Srm OUTPUT_NSZ, Ut.ut_name);
466dd371263Srm (void) strncpy(Ut.ut_name, "INVALID", NSZ);
467dd371263Srm }
468dd371263Srm /*
469dd371263Srm * Special case: OLD_TIME should be immediately followed by
470dd371263Srm * NEW_TIME.
471dd371263Srm * We make no attempt at alignment recovery between these
472dd371263Srm * two: if there's junk at this point in the input, then
473dd371263Srm * a NEW_TIME seen after the junk probably won't be the one
474dd371263Srm * we are looking for.
475dd371263Srm */
476dd371263Srm if (Ut.ut_type == OLD_TIME) {
477dd371263Srm /*
478dd371263Srm * Make recin refer to the expected NEW_TIME.
479dd371263Srm * Loop continuation will increment it again
480dd371263Srm * for the record we're about to read now.
481dd371263Srm */
482dd371263Srm recin += UTRSZ;
483dd371263Srm if (!fread(&Ut2, (size_t)UTRSZ, (size_t)1, Wtmpx)) {
484dd371263Srm wcomplain("input truncated after OLD_TIME - "
485dd371263Srm "giving up");
486dd371263Srm exit(EXIT_FAILURE);
4877c478bd9Sstevel@tonic-gate }
488dd371263Srm /*
489dd371263Srm * Rudimentary NEW_TIME sanity check. Not as thorough
490dd371263Srm * as in inrange(), but then we have redundancy from
491dd371263Srm * context here, since we're just after a plausible
492dd371263Srm * OLD_TIME record.
493dd371263Srm */
494dd371263Srm if ((Ut2.ut_type != NEW_TIME) ||
495dd371263Srm (strcmp(Ut2.ut_line, NTIME_MSG) != 0)) {
496dd371263Srm wcomplain("NEW_TIME expected but missing "
497dd371263Srm "after OLD_TIME - giving up");
498dd371263Srm exit(EXIT_FAILURE);
4997c478bd9Sstevel@tonic-gate }
500dd371263Srm lastok = recin;
501dd371263Srm if (multimode == B_TRUE)
502dd371263Srm setdtab(rectmp, &Ut, &Ut2);
503dd371263Srm rectmp += 2 * UTRSZ;
504dd371263Srm if ((fwrite(&Ut, UTRSZ, 1, Temp) < 1) ||
505dd371263Srm (fwrite(&Ut2, UTRSZ, 1, Temp) < 1)) {
506dd371263Srm perror("<temporary file>: fwrite");
507dd371263Srm exit(EXIT_FAILURE);
5087c478bd9Sstevel@tonic-gate }
509dd371263Srm continue;
510dd371263Srm }
511dd371263Srm if (fwrite(&Ut, UTRSZ, 1, Temp) < 1) {
512dd371263Srm perror("<temporary file>: fwrite");
513dd371263Srm exit(EXIT_FAILURE);
514dd371263Srm }
515dd371263Srm rectmp += UTRSZ;
516dd371263Srm }
517dd371263Srm if (want_ok == INRANGE_ALIGNED) {
518dd371263Srm wcomplain("EOF reached without recognizing another aligned "
519dd371263Srm "record with certainty. This file may need to be "
520dd371263Srm "repaired by hand.\n");
521dd371263Srm } else if (recovered == B_TRUE) {
522dd371263Srm /*
523dd371263Srm * There may have been a number of wcomplain() messages
524dd371263Srm * since we reported about the re-scan, so it bears repeating
525dd371263Srm * at the end that not all was well.
526dd371263Srm */
527dd371263Srm wcomplain("EOF reached after recovering from corruption "
528dd371263Srm "in the middle of the file. This file may need to be "
529dd371263Srm "repaired by hand.\n");
5307c478bd9Sstevel@tonic-gate }
5317c478bd9Sstevel@tonic-gate }
5327c478bd9Sstevel@tonic-gate
533dd371263Srm /*
534dd371263Srm * inrange: inspect what we hope to be one wtmpx record.
535dd371263Srm * Globals: Ut, lastmonth, nextmonth; recin, cur_input_name (diagnostics)
536dd371263Srm * Return values:
537dd371263Srm * INRANGE_ERR -- an inconsistency was detected, input file corrupted
538dd371263Srm * INRANGE_DROP -- Ut appears consistent but isn't of interest
539dd371263Srm * (of process type and outside the time range we want)
540dd371263Srm * INRANGE_PASS -- Ut appears consistent and this record is of interest
541dd371263Srm * INRANGE_ALIGNED -- same, and it is also redundant enough to be sure
542dd371263Srm * that we're correctly aligned on record boundaries
543dd371263Srm */
544dd371263Srm #define UNEXPECTED_UT_PID \
545dd371263Srm (Ut.ut_pid != 0) || \
546dd371263Srm (Ut.ut_exit.e_termination != 0) || \
547dd371263Srm (Ut.ut_exit.e_exit != 0)
548dd371263Srm
549dd371263Srm static inrange_t
inrange()5507c478bd9Sstevel@tonic-gate inrange()
5517c478bd9Sstevel@tonic-gate {
552dd371263Srm /* pid_t is signed so that fork() can return -1. Exploit this. */
553dd371263Srm if (Ut.ut_pid < 0) {
554dd371263Srm wcomplain("negative pid");
555dd371263Srm return (INRANGE_ERR);
556dd371263Srm }
5577c478bd9Sstevel@tonic-gate
558dd371263Srm /* the legal values for ut_type are enumerated in <utmp.h> */
559dd371263Srm switch (Ut.ut_type) {
560dd371263Srm case EMPTY:
561dd371263Srm if (UNEXPECTED_UT_PID) {
562dd371263Srm wcomplain("nonzero pid or status in EMPTY record");
563dd371263Srm return (INRANGE_ERR);
564dd371263Srm }
565dd371263Srm /*
566dd371263Srm * We'd like to have Ut.ut_user[0] == '\0' here, but sadly
567dd371263Srm * this isn't always so, so we can't rely on it.
568dd371263Srm */
569dd371263Srm return (INRANGE_DROP);
570dd371263Srm case RUN_LVL:
571dd371263Srm /* ut_line must have come from the RUNLVL_MSG pattern */
572dd371263Srm if (strncmp(Ut.ut_line, RUN_LEVEL_MSG, RLVLMSG_LEN) != 0) {
573dd371263Srm wcomplain("RUN_LVL record doesn't say `"
574dd371263Srm RUN_LEVEL_MSG "'");
575dd371263Srm return (INRANGE_ERR);
576dd371263Srm }
577dd371263Srm /*
578dd371263Srm * The ut_pid, termination, and exit status fields have
579dd371263Srm * special meaning in this case, and none of them is
580dd371263Srm * suitable for checking. And we won't insist on ut_user
581dd371263Srm * to always be an empty string.
582dd371263Srm */
583dd371263Srm return (INRANGE_ALIGNED);
584dd371263Srm case BOOT_TIME:
585dd371263Srm if (UNEXPECTED_UT_PID) {
586dd371263Srm wcomplain("nonzero pid or status in BOOT_TIME record");
587dd371263Srm return (INRANGE_ERR);
588dd371263Srm }
589dd371263Srm if (strcmp(Ut.ut_line, BOOT_MSG) != 0) {
590dd371263Srm wcomplain("BOOT_TIME record doesn't say `"
591dd371263Srm BOOT_MSG "'");
592dd371263Srm return (INRANGE_ERR);
593dd371263Srm }
594dd371263Srm return (INRANGE_ALIGNED);
595dd371263Srm case OLD_TIME:
596dd371263Srm if (UNEXPECTED_UT_PID) {
597dd371263Srm wcomplain("nonzero pid or status in OLD_TIME record");
598dd371263Srm return (INRANGE_ERR);
599dd371263Srm }
600dd371263Srm if (strcmp(Ut.ut_line, OTIME_MSG) != 0) {
601dd371263Srm wcomplain("OLD_TIME record doesn't say `"
602dd371263Srm OTIME_MSG "'");
603dd371263Srm return (INRANGE_ERR);
604dd371263Srm }
605dd371263Srm return (INRANGE_ALIGNED);
606dd371263Srm case NEW_TIME:
607dd371263Srm /*
608dd371263Srm * We don't actually expect to see any here. If they follow
609dd371263Srm * an OLD_TIME record as they should, they'll be handled on
610dd371263Srm * the fly in scanfile(). But we might still run into one
611dd371263Srm * if the input is somehow corrupted.
612dd371263Srm */
613dd371263Srm if (UNEXPECTED_UT_PID) {
614dd371263Srm wcomplain("nonzero pid or status in NEW_TIME record");
615dd371263Srm return (INRANGE_ERR);
616dd371263Srm }
617dd371263Srm if (strcmp(Ut.ut_line, NTIME_MSG) != 0) {
618dd371263Srm wcomplain("NEW_TIME record doesn't say `"
619dd371263Srm NTIME_MSG "'");
620dd371263Srm return (INRANGE_ERR);
621dd371263Srm }
622dd371263Srm return (INRANGE_ALIGNED);
623dd371263Srm
624dd371263Srm /* the four *_PROCESS ut_types have a lot in common */
625dd371263Srm case USER_PROCESS:
626dd371263Srm /*
627dd371263Srm * Catch two special cases first: psradm records have no id
628dd371263Srm * and no pid, while root login over FTP may not have a
629dd371263Srm * valid ut_user and may have garbage in ut_id[3].
630dd371263Srm */
631dd371263Srm if ((strcmp(Ut.ut_user, "psradm") == 0) &&
632dd371263Srm (Ut.ut_id[0] == '\0') &&
633dd371263Srm (Ut.ut_pid > 0)) {
634dd371263Srm if ((Ut.ut_xtime > lastmonth) &&
635dd371263Srm (Ut.ut_xtime < nextmonth)) {
636dd371263Srm return (INRANGE_ALIGNED);
637dd371263Srm } else {
638dd371263Srm return (INRANGE_DROP);
639dd371263Srm }
640dd371263Srm }
641dd371263Srm if ((Ut.ut_user[0] == '\0') &&
642dd371263Srm (strncmp(Ut.ut_id, "ftp", 3) == 0) &&
643dd371263Srm (strncmp(Ut.ut_line, "ftp", 3) == 0)) {
644dd371263Srm if ((Ut.ut_xtime > lastmonth) &&
645dd371263Srm (Ut.ut_xtime < nextmonth)) {
646dd371263Srm return (INRANGE_ALIGNED);
647dd371263Srm } else {
648dd371263Srm return (INRANGE_DROP);
649dd371263Srm }
650dd371263Srm }
651dd371263Srm /* FALLTHROUGH */
652dd371263Srm case LOGIN_PROCESS:
653dd371263Srm if (Ut.ut_user[0] == '\0') {
654dd371263Srm wcomplain("missing username in process record");
655dd371263Srm return (INRANGE_ERR);
656dd371263Srm }
657dd371263Srm /* FALLTHROUGH */
658dd371263Srm case INIT_PROCESS:
659dd371263Srm /*
660dd371263Srm * INIT_PROCESS and DEAD_PROCESS records can come with an
661dd371263Srm * empty ut_user in degenerate cases (e.g. syntax errors
662dd371263Srm * like a comment-only process field in /etc/inittab).
663dd371263Srm * But in an INIT_PROCESS, LOGIN_PROCESS, or USER_PROCESS
664dd371263Srm * record, we expect a respectable ut_pid.
665dd371263Srm */
666dd371263Srm if (Ut.ut_pid == 0) {
667dd371263Srm wcomplain("null pid in process record");
668dd371263Srm return (INRANGE_ERR);
669dd371263Srm }
670dd371263Srm /* FALLTHROUGH */
671dd371263Srm case DEAD_PROCESS:
672dd371263Srm /*
673dd371263Srm * DEAD_PROCESS records with a null ut_pid can be produced
674dd371263Srm * by gnome-terminal (normally seen in utmpx only, but they
675dd371263Srm * can leak into wtmpx in rare circumstances).
676dd371263Srm * Unfortunately, ut_id can't be relied on to contain
677dd371263Srm * anything in particular. (E.g., sshd might leave it
678dd371263Srm * 0-initialized.) This leaves almost no verifiable
679dd371263Srm * redundancy here beyond the ut_type.
680dd371263Srm * At least we insist on a reasonable timestamp.
681dd371263Srm */
682dd371263Srm if (Ut.ut_xtime <= 0) {
683dd371263Srm wcomplain("non-positive time in process record");
684dd371263Srm return (INRANGE_ERR);
685dd371263Srm }
686dd371263Srm if ((Ut.ut_xtime > lastmonth) &&
687dd371263Srm (Ut.ut_xtime < nextmonth)) {
688dd371263Srm return (INRANGE_PASS);
689dd371263Srm } else {
690dd371263Srm return (INRANGE_DROP);
691dd371263Srm }
692dd371263Srm case ACCOUNTING:
693dd371263Srm /*
694dd371263Srm * If we recognize one of the three reason strings passed
695dd371263Srm * by the /usr/lib/acct shell scripts to acctwtmp, we
696dd371263Srm * exploit the available redundancy they offer. But
697dd371263Srm * acctwtmp could have been invoked by custom scripts or
698dd371263Srm * interactively with other reason strings in the first
699dd371263Srm * argument, so anything we don't recognize does not
700dd371263Srm * constitute evidence for corruption.
701dd371263Srm */
702dd371263Srm if ((strcmp(Ut.ut_line, RUNACCT_MSG) != 0) &&
703dd371263Srm (strcmp(Ut.ut_line, ACCTG_ON_MSG) != 0) &&
704dd371263Srm (strcmp(Ut.ut_line, ACCTG_OFF_MSG) != 0)) {
705dd371263Srm return (INRANGE_DROP);
706dd371263Srm }
707dd371263Srm return (INRANGE_ALIGNED);
708dd371263Srm case DOWN_TIME:
709dd371263Srm if (UNEXPECTED_UT_PID) {
710dd371263Srm wcomplain("nonzero pid or status in DOWN_TIME record");
711dd371263Srm return (INRANGE_ERR);
712dd371263Srm }
713dd371263Srm if (strcmp(Ut.ut_line, DOWN_MSG) != 0) {
714dd371263Srm wcomplain("DOWN_TIME record doesn't say `"
715dd371263Srm DOWN_MSG "'");
716dd371263Srm return (INRANGE_ERR);
717dd371263Srm }
718dd371263Srm return (INRANGE_ALIGNED);
719dd371263Srm default:
720dd371263Srm wcomplain("ut_type out of range");
721dd371263Srm return (INRANGE_ERR);
722dd371263Srm }
723dd371263Srm /* NOTREACHED */
7247c478bd9Sstevel@tonic-gate }
7257c478bd9Sstevel@tonic-gate
7267c478bd9Sstevel@tonic-gate static void
wcomplain(char * msg)727dd371263Srm wcomplain(char *msg)
7287c478bd9Sstevel@tonic-gate {
729dd371263Srm (void) fprintf(stderr, "%s: offset %lld: %s\n", cur_input_name,
730dd371263Srm (longlong_t)recin, msg);
7317c478bd9Sstevel@tonic-gate }
732