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