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 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 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 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 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 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 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 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 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 729 wcomplain(char *msg) 730 { 731 (void) fprintf(stderr, "%s: offset %lld: %s\n", cur_input_name, 732 (longlong_t)recin, msg); 733 } 734