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