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