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