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 2010 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * 25 * write binary audit records directly to a file. 26 */ 27 28 #define DEBUG 0 29 30 #if DEBUG 31 #define DPRINT(x) {fprintf x; } 32 #else 33 #define DPRINT(x) 34 #endif 35 36 /* 37 * auditd_plugin_open(), auditd_plugin() and auditd_plugin_close() 38 * implement a replacable library for use by auditd; they are a 39 * project private interface and may change without notice. 40 * 41 */ 42 43 #include <assert.h> 44 #include <bsm/audit.h> 45 #include <bsm/audit_record.h> 46 #include <bsm/libbsm.h> 47 #include <errno.h> 48 #include <fcntl.h> 49 #include <libintl.h> 50 #include <netdb.h> 51 #include <pthread.h> 52 #include <secdb.h> 53 #include <signal.h> 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <string.h> 57 #include <sys/param.h> 58 #include <sys/types.h> 59 #include <time.h> 60 #include <tzfile.h> 61 #include <unistd.h> 62 #include <sys/vfs.h> 63 #include <limits.h> 64 #include <syslog.h> 65 #include <security/auditd.h> 66 #include <audit_plugin.h> 67 68 #define AUDIT_DATE_SZ 14 69 #define AUDIT_FNAME_SZ 2 * AUDIT_DATE_SZ + 2 + MAXHOSTNAMELEN 70 71 /* per-directory status */ 72 #define SOFT_SPACE 0 /* minfree or less space available */ 73 #define PLENTY_SPACE 1 /* more than minfree available */ 74 #define SPACE_FULL 2 /* out of space */ 75 76 #define AVAIL_MIN 50 /* If there are less that this number */ 77 /* of blocks avail, the filesystem is */ 78 /* presumed full. */ 79 80 #define ALLHARD_DELAY 20 /* Call audit_warn(allhard) every 20 seconds */ 81 82 /* minimum reasonable size in bytes to roll over an audit file */ 83 #define FSIZE_MIN 512000 84 85 /* 86 * The directory list is a circular linked list. It is pointed into by 87 * activeDir. Each element contains the pointer to the next 88 * element, the directory pathname, a flag for how much space there is 89 * in the directory's filesystem, and a file handle. Since a new 90 * directory list can be created from auditd_plugin_open() while the 91 * current list is in use, activeDir is protected by log_mutex. 92 */ 93 typedef struct dirlist_s dirlist_t; 94 struct dirlist_s { 95 dirlist_t *dl_next; 96 int dl_space; 97 int dl_flags; 98 char *dl_dirname; 99 char *dl_filename; /* file name (not path) if open */ 100 int dl_fd; /* file handle, -1 unless open */ 101 }; 102 /* 103 * Defines for dl_flags 104 */ 105 #define SOFT_WARNED 0x0001 /* already did soft warning for this dir */ 106 #define HARD_WARNED 0x0002 /* already did hard warning for this dir */ 107 108 #if DEBUG 109 static FILE *dbfp; /* debug file */ 110 #endif 111 112 static pthread_mutex_t log_mutex; 113 static int binfile_is_open = 0; 114 115 static int minfree = -1; 116 static int minfreeblocks; /* minfree in blocks */ 117 118 static dirlist_t *activeDir = NULL; /* current directory */ 119 static dirlist_t *startdir; /* first dir in the ring */ 120 static int activeCount = 0; /* number of dirs in the ring */ 121 122 static int openNewFile = 0; /* need to open a new file */ 123 static int hung_count = 0; /* count of audit_warn hard */ 124 125 /* flag from audit_plugin_open to audit_plugin_close */ 126 static int am_open = 0; 127 /* preferred dir state */ 128 static int fullness_state = PLENTY_SPACE; 129 130 /* 131 * These are used to implement a maximum size for the auditing 132 * file. binfile_maxsize is set via the 'p_fsize' parameter to the 133 * audit_binfile plugin. 134 */ 135 static uint_t binfile_cursize = 0; 136 static uint_t binfile_maxsize = 0; 137 138 static int open_log(dirlist_t *); 139 140 static void 141 freedirlist(dirlist_t *head) 142 { 143 dirlist_t *n1, *n2; 144 /* 145 * Free up the old directory list if any 146 */ 147 if (head != NULL) { 148 n1 = head; 149 do { 150 n2 = n1->dl_next; 151 free(n1->dl_dirname); 152 free(n1->dl_filename); 153 free(n1); 154 n1 = n2; 155 } while (n1 != head); 156 } 157 } 158 159 160 /* 161 * add to a linked list of directories available for writing 162 * 163 */ 164 165 static int 166 growauditlist(dirlist_t **listhead, char *dirlist, 167 dirlist_t *endnode, int *count) 168 { 169 dirlist_t *node; 170 char *bs, *be; 171 dirlist_t **node_p; 172 char *dirname; 173 char *remainder; 174 175 DPRINT((dbfp, "binfile: dirlist=%s\n", dirlist)); 176 177 if (*listhead == NULL) 178 node_p = listhead; 179 else 180 node_p = &(endnode->dl_next); 181 182 node = NULL; 183 while ((dirname = strtok_r(dirlist, ",", &remainder)) != NULL) { 184 dirlist = NULL; 185 186 DPRINT((dbfp, "binfile: p_dir = %s\n", dirname)); 187 188 (*count)++; 189 node = malloc(sizeof (dirlist_t)); 190 if (node == NULL) 191 return (AUDITD_NO_MEMORY); 192 193 node->dl_flags = 0; 194 node->dl_filename = NULL; 195 node->dl_fd = -1; 196 node->dl_space = PLENTY_SPACE; 197 198 node->dl_dirname = malloc((unsigned)strlen(dirname) + 1); 199 if (node->dl_dirname == NULL) 200 return (AUDITD_NO_MEMORY); 201 202 bs = dirname; 203 while ((*bs == ' ') || (*bs == '\t')) /* trim blanks */ 204 bs++; 205 be = bs + strlen(bs) - 1; 206 while (be > bs) { /* trim trailing blanks */ 207 if ((*bs != ' ') && (*bs != '\t')) 208 break; 209 be--; 210 } 211 *(be + 1) = '\0'; 212 (void) strlcpy(node->dl_dirname, bs, AUDIT_FNAME_SZ); 213 214 if (*listhead != NULL) 215 node->dl_next = *listhead; 216 else 217 node->dl_next = node; 218 *node_p = node; 219 node_p = &(node->dl_next); 220 221 } 222 return (0); 223 } 224 225 /* 226 * create a linked list of directories available for writing 227 * 228 * if a list already exists, the two are compared and the new one is 229 * used only if it is different than the old. 230 * 231 * returns -2 for new or changed list, 0 for unchanged list and -1 for 232 * error. (Positive returns are for AUDITD_<error code> values) 233 * 234 */ 235 236 static int 237 loadauditlist(char *dirstr, char *minfreestr) 238 { 239 char buf[MAXPATHLEN]; 240 char *bs, *be; 241 dirlist_t *node, *n1, *n2; 242 dirlist_t **node_p; 243 dirlist_t *listhead = NULL; 244 dirlist_t *thisdir; 245 int acresult; 246 int node_count = 0; 247 int rc; 248 int temp_minfree; 249 au_acinfo_t *ach; 250 251 static dirlist_t *activeList = NULL; /* directory list */ 252 253 DPRINT((dbfp, "binfile: Loading audit list from auditcontrol\n")); 254 255 /* 256 * Build new directory list 257 */ 258 /* part 1 -- using pre Sol 10 audit_control directives */ 259 node_p = &listhead; 260 261 ach = _openac(NULL); 262 if (ach == NULL) 263 return (-1); 264 265 /* at least one directory is needed */ 266 while ((acresult = _getacdir(ach, buf, sizeof (buf))) == 0 || 267 acresult == 2 || acresult == -3) { 268 /* 269 * loop if the result is 0 (success), 2 (a warning 270 * that the audit_control file has been rewound), 271 * or -3 (a directory entry was found, but it 272 * was badly formatted. 273 */ 274 if (acresult == 0) { 275 /* 276 * A directory entry was found. 277 */ 278 node_count++; 279 node = malloc(sizeof (dirlist_t)); 280 if (node == NULL) 281 return (AUDITD_NO_MEMORY); 282 283 node->dl_flags = 0; 284 node->dl_fd = -1; 285 node->dl_space = PLENTY_SPACE; 286 node->dl_filename = NULL; 287 288 node->dl_dirname = malloc((unsigned)strlen(buf) + 1); 289 if (node->dl_dirname == NULL) 290 return (AUDITD_NO_MEMORY); 291 292 bs = buf; 293 while ((*bs == ' ') || (*bs == '\t')) 294 bs++; 295 be = bs + strlen(bs) - 1; 296 while (be > bs) { /* trim trailing blanks */ 297 if ((*bs != ' ') && (*bs != '\t')) 298 break; 299 be--; 300 } 301 *(be + 1) = '\0'; 302 (void) strlcpy(node->dl_dirname, bs, AUDIT_FNAME_SZ); 303 304 if (listhead != NULL) 305 node->dl_next = listhead; 306 else 307 node->dl_next = node; 308 *node_p = node; 309 node_p = &(node->dl_next); 310 } 311 } /* end of getacdir while */ 312 /* 313 * part 2 -- use directories and minfree from the (new as of Sol 10) 314 * plugin directive 315 */ 316 if (dirstr != NULL) { 317 if (node_count == 0) { 318 listhead = NULL; 319 node = NULL; 320 } 321 rc = growauditlist(&listhead, dirstr, node, &node_count); 322 if (rc) 323 return (rc); 324 } 325 if (node_count == 0) { 326 /* 327 * there was a problem getting the directory 328 * list or remote host info from the audit_control file 329 * even though auditd thought there was at least 1 good 330 * entry 331 */ 332 DPRINT((dbfp, "binfile: " 333 "problem getting directory / libpath list " 334 "from audit_control.\n")); 335 336 _endac(ach); 337 return (-1); 338 } 339 #if DEBUG 340 /* print out directory list */ 341 342 if (listhead != NULL) { 343 fprintf(dbfp, "Directory list:\n\t%s\n", listhead->dl_dirname); 344 thisdir = listhead->dl_next; 345 346 while (thisdir != listhead) { 347 fprintf(dbfp, "\t%s\n", thisdir->dl_dirname); 348 thisdir = thisdir->dl_next; 349 } 350 } 351 #endif /* DEBUG */ 352 thisdir = listhead; 353 /* 354 * See if the list has changed. 355 * If there was a change rc = 0 if no change, else 1 356 */ 357 rc = 0; /* no change */ 358 359 if (node_count == activeCount) { 360 n1 = listhead; 361 n2 = activeList; 362 do { 363 if (strcmp(n1->dl_dirname, n2->dl_dirname) != 0) { 364 DPRINT((dbfp, 365 "binfile: new dirname = %s\n" 366 "binfile: old dirname = %s\n", 367 n1->dl_dirname, 368 n2->dl_dirname)); 369 rc = -2; 370 break; 371 } 372 n1 = n1->dl_next; 373 n2 = n2->dl_next; 374 } while ((n1 != listhead) && (n2 != activeList)); 375 } else { 376 DPRINT((dbfp, "binfile: old dir count = %d\n" 377 "binfile: new dir count = %d\n", 378 activeCount, node_count)); 379 rc = -2; 380 } 381 if (rc == -2) { 382 (void) pthread_mutex_lock(&log_mutex); 383 DPRINT((dbfp, "loadauditlist: close / open log\n")); 384 if (open_log(listhead) == 0) { 385 openNewFile = 1; /* try again later */ 386 } else { 387 openNewFile = 0; 388 } 389 freedirlist(activeList); /* old list */ 390 activeList = listhead; /* new list */ 391 activeDir = startdir = thisdir; 392 activeCount = node_count; 393 (void) pthread_mutex_unlock(&log_mutex); 394 } else 395 freedirlist(listhead); 396 /* 397 * Get the minfree value. If minfree comes in via the attribute 398 * list, ignore the possibility it may also be listed on a separate 399 * audit_control line. 400 */ 401 if (minfreestr != NULL) 402 temp_minfree = atoi(minfreestr); 403 else if (!(_getacmin(ach, &temp_minfree) == 0)) 404 temp_minfree = 0; 405 406 if ((temp_minfree < 0) || (temp_minfree > 100)) 407 temp_minfree = 0; 408 409 if (minfree != temp_minfree) { 410 DPRINT((dbfp, "minfree: old = %d, new = %d\n", 411 minfree, temp_minfree)); 412 rc = -2; /* data change */ 413 minfree = temp_minfree; 414 } 415 _endac(ach); 416 417 return (rc); 418 } 419 420 421 /* 422 * getauditdate - get the current time (GMT) and put it in the form 423 * yyyymmddHHMMSS . 424 */ 425 static void 426 getauditdate(char *date) 427 { 428 struct timeval tp; 429 struct timezone tzp; 430 struct tm tm; 431 432 (void) gettimeofday(&tp, &tzp); 433 tm = *gmtime(&tp.tv_sec); 434 /* 435 * NOTE: if we want to use gmtime, we have to be aware that the 436 * structure only keeps the year as an offset from TM_YEAR_BASE. 437 * I have used TM_YEAR_BASE in this code so that if they change 438 * this base from 1900 to 2000, it will hopefully mean that this 439 * code does not have to change. TM_YEAR_BASE is defined in 440 * tzfile.h . 441 */ 442 (void) sprintf(date, "%.4d%.2d%.2d%.2d%.2d%.2d", 443 tm.tm_year + TM_YEAR_BASE, tm.tm_mon + 1, tm.tm_mday, 444 tm.tm_hour, tm.tm_min, tm.tm_sec); 445 } 446 447 448 449 /* 450 * write_file_token - put the file token into the audit log 451 */ 452 static int 453 write_file_token(int fd, char *name) 454 { 455 adr_t adr; /* xdr ptr */ 456 struct timeval tv; /* time now */ 457 char for_adr[AUDIT_FNAME_SZ + AUDIT_FNAME_SZ]; /* plenty of room */ 458 char token_id; 459 short i; 460 461 (void) gettimeofday(&tv, (struct timezone *)0); 462 i = strlen(name) + 1; 463 adr_start(&adr, for_adr); 464 #ifdef _LP64 465 token_id = AUT_OTHER_FILE64; 466 adr_char(&adr, &token_id, 1); 467 adr_int64(&adr, (int64_t *)& tv, 2); 468 #else 469 token_id = AUT_OTHER_FILE32; 470 adr_char(&adr, &token_id, 1); 471 adr_int32(&adr, (int32_t *)& tv, 2); 472 #endif 473 474 adr_short(&adr, &i, 1); 475 adr_char(&adr, name, i); 476 477 if (write(fd, for_adr, adr_count(&adr)) < 0) { 478 DPRINT((dbfp, "binfile: Bad write\n")); 479 return (errno); 480 } 481 return (0); 482 } 483 484 /* 485 * close_log - close the file if open. Also put the name of the 486 * new log file in the trailer, and rename the old file 487 * to oldname. The caller must hold log_mutext while calling 488 * close_log since any change to activeDir is a complete redo 489 * of all it points to. 490 * arguments - 491 * oldname - the new name for the file to be closed 492 * newname - the name of the new log file (for the trailer) 493 */ 494 static void 495 close_log(dirlist_t *currentdir, char *oname, char *newname) 496 { 497 char auditdate[AUDIT_DATE_SZ+1]; 498 char *name; 499 char oldname[AUDIT_FNAME_SZ+1]; 500 501 if ((currentdir == NULL) || (currentdir->dl_fd == -1)) 502 return; 503 /* 504 * If oldname is blank, we were called by auditd_plugin_close() 505 * instead of by open_log, so we need to update our name. 506 */ 507 (void) strlcpy(oldname, oname, AUDIT_FNAME_SZ); 508 509 if (strcmp(oldname, "") == 0) { 510 getauditdate(auditdate); 511 512 assert(currentdir->dl_filename != NULL); 513 514 (void) strlcpy(oldname, currentdir->dl_filename, 515 AUDIT_FNAME_SZ); 516 517 name = strrchr(oldname, '/') + 1; 518 (void) memcpy(name + AUDIT_DATE_SZ + 1, auditdate, 519 AUDIT_DATE_SZ); 520 } 521 /* 522 * Write the trailer record and rename and close the file. 523 * If any of the write, rename, or close fail, ignore it 524 * since there is not much else we can do and the next open() 525 * will trigger the necessary full directory logic. 526 * 527 * newname is "" if binfile is being closed down. 528 */ 529 (void) write_file_token(currentdir->dl_fd, newname); 530 if (currentdir->dl_fd >= 0) { 531 (void) fsync(currentdir->dl_fd); 532 (void) close(currentdir->dl_fd); 533 } 534 currentdir->dl_fd = -1; 535 (void) rename(currentdir->dl_filename, oldname); 536 537 DPRINT((dbfp, "binfile: Log closed %s\n", oldname)); 538 539 free(currentdir->dl_filename); 540 currentdir->dl_filename = NULL; 541 } 542 543 544 /* 545 * open_log - open a new file in the current directory. If a 546 * file is already open, close it. 547 * 548 * return 1 if ok, 0 if all directories are full. 549 * 550 * lastOpenDir - used to get the oldfile name (and change it), 551 * to close the oldfile. 552 * 553 * The caller must hold log_mutex while calling open_log. 554 * 555 */ 556 static int 557 open_log(dirlist_t *current_dir) 558 { 559 char auditdate[AUDIT_DATE_SZ + 1]; 560 char oldname[AUDIT_FNAME_SZ + 1] = ""; 561 char newname[AUDIT_FNAME_SZ + 1]; 562 char *name; /* pointer into oldname */ 563 int opened; 564 int error = 0; 565 int newfd = 0; 566 567 static char host[MAXHOSTNAMELEN + 1] = ""; 568 /* previous directory with open log file */ 569 static dirlist_t *lastOpenDir = NULL; 570 571 if (host[0] == '\0') 572 (void) gethostname(host, MAXHOSTNAMELEN); 573 574 /* Get a filename which does not already exist */ 575 opened = 0; 576 while (!opened) { 577 getauditdate(auditdate); 578 (void) snprintf(newname, AUDIT_FNAME_SZ, 579 "%s/%s.not_terminated.%s", 580 current_dir->dl_dirname, auditdate, host); 581 newfd = open(newname, 582 O_RDWR | O_APPEND | O_CREAT | O_EXCL, 0640); 583 if (newfd < 0) { 584 switch (errno) { 585 case EEXIST: 586 DPRINT((dbfp, 587 "open_log says duplicate for %s " 588 "(will try another)\n", newname)); 589 (void) sleep(1); 590 break; 591 default: 592 /* open failed */ 593 DPRINT((dbfp, 594 "open_log says full for %s: %s\n", 595 newname, strerror(errno))); 596 current_dir->dl_space = SPACE_FULL; 597 current_dir = current_dir->dl_next; 598 return (0); 599 } /* switch */ 600 } else 601 opened = 1; 602 } /* while */ 603 604 /* 605 * When we get here, we have opened our new log file. 606 * Now we need to update the name of the old file to 607 * store in this file's header. lastOpenDir may point 608 * to current_dir if the list is only one entry long and 609 * there is only one list. 610 */ 611 if ((lastOpenDir != NULL) && (lastOpenDir->dl_filename != NULL)) { 612 (void) strlcpy(oldname, lastOpenDir->dl_filename, 613 AUDIT_FNAME_SZ); 614 name = (char *)strrchr(oldname, '/') + 1; 615 616 (void) memcpy(name + AUDIT_DATE_SZ + 1, auditdate, 617 AUDIT_DATE_SZ); 618 619 close_log(lastOpenDir, oldname, newname); 620 } 621 error = write_file_token(newfd, oldname); 622 if (error) { 623 /* write token failed */ 624 (void) close(newfd); 625 626 current_dir->dl_space = SPACE_FULL; 627 current_dir->dl_fd = -1; 628 free(current_dir->dl_filename); 629 current_dir->dl_filename = NULL; 630 current_dir = current_dir->dl_next; 631 return (0); 632 } else { 633 lastOpenDir = current_dir; 634 current_dir->dl_fd = newfd; 635 current_dir->dl_filename = strdup(newname); 636 637 /* 638 * New file opened, so reset file size statistic (used 639 * to ensure audit log does not grow above size limit 640 * set by p_fsize). 641 */ 642 binfile_cursize = 0; 643 644 (void) __logpost(newname); 645 646 DPRINT((dbfp, "binfile: Log opened: %s\n", newname)); 647 return (1); 648 } 649 } 650 651 #define IGNORE_SIZE 8192 652 /* 653 * spacecheck - determine whether the given directory's filesystem 654 * has the at least the space requested. Also set the space 655 * value in the directory list structure. If the caller 656 * passes other than PLENTY_SPACE or SOFT_SPACE, the caller should 657 * ignore the return value. Otherwise, 0 = less than the 658 * requested space is available, 1 = at least the requested space 659 * is available. 660 * 661 * log_mutex must be held by the caller 662 * 663 * -1 is returned if stat fails 664 * 665 * IGNORE_SIZE is one page (Sol 9 / 10 timeframe) and is the default 666 * buffer size written for Sol 9 and earlier. To keep the same accuracy 667 * for the soft limit check as before, spacecheck checks for space 668 * remaining IGNORE_SIZE bytes. This reduces the number of statvfs() 669 * calls and related math. 670 * 671 * globals - 672 * minfree - the soft limit, i.e., the % of filesystem to reserve 673 */ 674 static int 675 spacecheck(dirlist_t *thisdir, int test_limit, size_t next_buf_size) 676 { 677 struct statvfs sb; 678 static int ignore_size = 0; 679 680 ignore_size += next_buf_size; 681 682 if ((test_limit == PLENTY_SPACE) && (ignore_size < IGNORE_SIZE)) 683 return (1); 684 685 assert(thisdir != NULL); 686 687 if (statvfs(thisdir->dl_dirname, &sb) < 0) { 688 thisdir->dl_space = SPACE_FULL; 689 minfreeblocks = AVAIL_MIN; 690 return (-1); 691 } else { 692 minfreeblocks = ((minfree * sb.f_blocks) / 100) + AVAIL_MIN; 693 694 if (sb.f_bavail < AVAIL_MIN) 695 thisdir->dl_space = SPACE_FULL; 696 else if (sb.f_bavail > minfreeblocks) { 697 thisdir->dl_space = fullness_state = PLENTY_SPACE; 698 ignore_size = 0; 699 } else 700 thisdir->dl_space = SOFT_SPACE; 701 } 702 if (thisdir->dl_space == PLENTY_SPACE) 703 return (1); 704 705 return (thisdir->dl_space == test_limit); 706 } 707 708 /* 709 * Parses p_fsize value and contains it within the range FSIZE_MIN and 710 * INT_MAX so using uints won't cause an undetected overflow of 711 * INT_MAX. Defaults to 0 if the value is invalid or is missing. 712 */ 713 static void 714 save_maxsize(char *maxsize) { 715 /* 716 * strtol() returns a long which could be larger than int so 717 * store here for sanity checking first 718 */ 719 long proposed_maxsize; 720 721 if (maxsize != NULL) { 722 /* 723 * There is no explicit error return from strtol() so 724 * we may need to depend on the value of errno. 725 */ 726 errno = 0; 727 proposed_maxsize = strtol(maxsize, (char **)NULL, 10); 728 729 /* 730 * If sizeof(long) is greater than sizeof(int) on this 731 * platform, proposed_maxsize might be greater than 732 * INT_MAX without it being reported as ERANGE. 733 */ 734 if ((errno == ERANGE) || 735 ((proposed_maxsize != 0) && 736 (proposed_maxsize < FSIZE_MIN)) || 737 (proposed_maxsize > INT_MAX)) { 738 binfile_maxsize = 0; 739 DPRINT((dbfp, "binfile: p_fsize parameter out of " 740 "range: %s\n", maxsize)); 741 /* 742 * Inform administrator of the error via 743 * syslog 744 */ 745 __audit_syslog("audit_binfile.so", 746 LOG_CONS | LOG_NDELAY, 747 LOG_DAEMON, LOG_ERR, 748 gettext("p_fsize parameter out of range\n")); 749 } else { 750 binfile_maxsize = proposed_maxsize; 751 } 752 } else { /* p_fsize string was not present */ 753 binfile_maxsize = 0; 754 } 755 756 DPRINT((dbfp, "binfile: set maxsize to %d\n", binfile_maxsize)); 757 } 758 759 /* 760 * auditd_plugin() writes a buffer to the currently open file. The 761 * global "openNewFile" is used to force a new log file for cases such 762 * as the initial open, when minfree is reached, the p_fsize value is 763 * exceeded or the current file system fills up, and "audit -s" with 764 * changed parameters. For "audit -n" a new log file is opened 765 * immediately in auditd_plugin_open(). 766 * 767 * This function manages one or more audit directories as follows: 768 * 769 * If the current open file is in a directory that has not 770 * reached the soft limit, write the input data and return. 771 * 772 * Scan the list of directories for one which has not reached 773 * the soft limit; if one is found, write and return. Such 774 * a writable directory is in "PLENTY_SPACE" state. 775 * 776 * Scan the list of directories for one which has not reached 777 * the hard limit; if one is found, write and return. This 778 * directory in in "SOFT_SPACE" state. 779 * 780 * Oh, and if a write fails, handle it like a hard space limit. 781 * 782 * audit_warn (via __audit_dowarn()) is used to alert an operator 783 * at various levels of fullness. 784 */ 785 /* ARGSUSED */ 786 auditd_rc_t 787 auditd_plugin(const char *input, size_t in_len, uint64_t sequence, char **error) 788 { 789 auditd_rc_t rc = AUDITD_FAIL; 790 int open_status; 791 size_t out_len; 792 /* LINTED */ 793 int statrc; 794 /* avoid excess audit_warnage */ 795 static int allsoftfull_warning = 0; 796 static int allhard_pause = 0; 797 static struct timeval next_allhard; 798 struct timeval now; 799 #if DEBUG 800 static char *last_file_written_to = NULL; 801 static uint64_t last_sequence = 0; 802 static uint64_t write_count = 0; 803 804 if ((last_sequence > 0) && (sequence != last_sequence + 1)) 805 fprintf(dbfp, "binfile: buffer sequence=%llu but prev=%llu=n", 806 sequence, last_sequence); 807 last_sequence = sequence; 808 809 fprintf(dbfp, "binfile: input seq=%llu, len=%d\n", sequence, in_len); 810 #endif 811 *error = NULL; 812 /* 813 * lock is for activeDir, referenced by open_log() and close_log() 814 */ 815 (void) pthread_mutex_lock(&log_mutex); 816 817 /* 818 * If this would take us over the maximum size, open a new 819 * file, unless maxsize is 0, in which case growth of the 820 * audit log is unrestricted. 821 */ 822 if ((binfile_maxsize != 0) && 823 ((binfile_cursize + in_len) > binfile_maxsize)) { 824 DPRINT((dbfp, "binfile: maxsize exceeded, opening new audit " 825 "file.\n")); 826 openNewFile = 1; 827 } 828 829 while (rc == AUDITD_FAIL) { 830 open_status = 1; 831 if (openNewFile) { 832 open_status = open_log(activeDir); 833 if (open_status == 1) /* ok */ 834 openNewFile = 0; 835 } 836 /* 837 * consider "space ok" return and error return the same; 838 * a -1 means spacecheck couldn't check for space. 839 */ 840 if ((open_status == 1) && 841 (statrc = spacecheck(activeDir, fullness_state, 842 in_len)) != 0) { 843 #if DEBUG 844 DPRINT((dbfp, "binfile: returned from spacecheck\n")); 845 /* 846 * The last copy of last_file_written_to is 847 * never free'd, so there will be one open 848 * memory reference on exit. It's debug only. 849 */ 850 if ((last_file_written_to != NULL) && 851 (strcmp(last_file_written_to, 852 activeDir->dl_filename) != 0)) { 853 DPRINT((dbfp, "binfile: now writing to %s\n", 854 activeDir->dl_filename)); 855 free(last_file_written_to); 856 } 857 DPRINT((dbfp, "binfile: finished some debug stuff\n")); 858 last_file_written_to = 859 strdup(activeDir->dl_filename); 860 #endif 861 out_len = write(activeDir->dl_fd, input, in_len); 862 DPRINT((dbfp, "binfile: finished the write\n")); 863 864 binfile_cursize += out_len; 865 866 if (out_len == in_len) { 867 DPRINT((dbfp, 868 "binfile: write_count=%llu, sequence=%llu," 869 " l=%u\n", 870 ++write_count, sequence, out_len)); 871 allsoftfull_warning = 0; 872 activeDir->dl_flags = 0; 873 874 rc = AUDITD_SUCCESS; 875 break; 876 } else if (!(activeDir->dl_flags & HARD_WARNED)) { 877 DPRINT((dbfp, 878 "binfile: write failed, sequence=%llu, " 879 "l=%u\n", sequence, out_len)); 880 DPRINT((dbfp, "hard warning sent.\n")); 881 __audit_dowarn("hard", activeDir->dl_dirname, 882 0); 883 884 activeDir->dl_flags |= HARD_WARNED; 885 } 886 } else { 887 DPRINT((dbfp, "binfile: statrc=%d, fullness_state=%d\n", 888 statrc, fullness_state)); 889 if (!(activeDir->dl_flags & SOFT_WARNED) && 890 (activeDir->dl_space == SOFT_SPACE)) { 891 DPRINT((dbfp, "soft warning sent\n")); 892 __audit_dowarn("soft", 893 activeDir->dl_dirname, 0); 894 activeDir->dl_flags |= SOFT_WARNED; 895 } 896 if (!(activeDir->dl_flags & HARD_WARNED) && 897 (activeDir->dl_space == SPACE_FULL)) { 898 DPRINT((dbfp, "hard warning sent.\n")); 899 __audit_dowarn("hard", 900 activeDir->dl_dirname, 0); 901 activeDir->dl_flags |= HARD_WARNED; 902 } 903 } 904 DPRINT((dbfp, "binfile: activeDir=%s, next=%s\n", 905 activeDir->dl_dirname, activeDir->dl_next->dl_dirname)); 906 907 activeDir = activeDir->dl_next; 908 openNewFile = 1; 909 910 if (activeDir == startdir) { /* full circle */ 911 if (fullness_state == PLENTY_SPACE) { /* once */ 912 fullness_state = SOFT_SPACE; 913 if (allsoftfull_warning == 0) { 914 allsoftfull_warning++; 915 __audit_dowarn("allsoft", "", 0); 916 } 917 } else { /* full circle twice */ 918 if ((hung_count > 0) && !allhard_pause) { 919 allhard_pause = 1; 920 (void) gettimeofday(&next_allhard, 921 NULL); 922 next_allhard.tv_sec += ALLHARD_DELAY; 923 } 924 925 if (allhard_pause) { 926 (void) gettimeofday(&now, NULL); 927 if (now.tv_sec >= next_allhard.tv_sec) { 928 allhard_pause = 0; 929 __audit_dowarn("allhard", "", 930 ++hung_count); 931 } 932 } else { 933 __audit_dowarn("allhard", "", 934 ++hung_count); 935 } 936 minfreeblocks = AVAIL_MIN; 937 rc = AUDITD_RETRY; 938 *error = strdup(gettext( 939 "all partitions full\n")); 940 (void) __logpost(""); 941 } 942 } 943 } 944 (void) pthread_mutex_unlock(&log_mutex); 945 946 return (rc); 947 } 948 949 950 /* 951 * the open function uses getacdir() and getacmin to determine which 952 * directories to use and when to switch. It takes no inputs. 953 * 954 * It may be called multiple times as auditd handles SIGHUP and SIGUSR1 955 * corresponding to the audit(1M) flags -s and -n 956 * 957 * kvlist is NULL only if auditd caught a SIGUSR1, so after the first 958 * time open is called, the reason is -s if kvlist != NULL and -n 959 * otherwise. 960 * 961 */ 962 963 auditd_rc_t 964 auditd_plugin_open(const kva_t *kvlist, char **ret_list, char **error) 965 { 966 int rc = 0; 967 int status; 968 int reason; 969 char *dirlist; 970 char *minfree; 971 char *maxsize; 972 kva_t *kv; 973 974 *error = NULL; 975 *ret_list = NULL; 976 kv = (kva_t *)kvlist; 977 978 if (am_open) { 979 if (kvlist == NULL) 980 reason = 1; /* audit -n */ 981 else 982 reason = 2; /* audit -s */ 983 } else { 984 reason = 0; /* initial open */ 985 #if DEBUG 986 dbfp = __auditd_debug_file_open(); 987 #endif 988 } 989 DPRINT((dbfp, "binfile: am_open=%d, reason=%d\n", am_open, reason)); 990 991 am_open = 1; 992 993 if (kvlist == NULL) { 994 dirlist = NULL; 995 minfree = NULL; 996 maxsize = NULL; 997 } else { 998 dirlist = kva_match(kv, "p_dir"); 999 minfree = kva_match(kv, "p_minfree"); 1000 maxsize = kva_match(kv, "p_fsize"); 1001 } 1002 switch (reason) { 1003 case 0: /* initial open */ 1004 if (!binfile_is_open) 1005 (void) pthread_mutex_init(&log_mutex, NULL); 1006 binfile_is_open = 1; 1007 openNewFile = 1; 1008 /* FALLTHRU */ 1009 case 2: /* audit -s */ 1010 /* handle p_fsize parameter */ 1011 save_maxsize(maxsize); 1012 1013 fullness_state = PLENTY_SPACE; 1014 status = loadauditlist(dirlist, minfree); 1015 1016 if (status == -1) { 1017 (void) __logpost(""); 1018 *error = strdup(gettext("no directories configured")); 1019 return (AUDITD_RETRY); 1020 } else if (status == AUDITD_NO_MEMORY) { 1021 (void) __logpost(""); 1022 *error = strdup(gettext("no memory")); 1023 return (status); 1024 } else { /* status is 0 or -2 (no change or changed) */ 1025 hung_count = 0; 1026 DPRINT((dbfp, "binfile: loadauditlist returned %d\n", 1027 status)); 1028 } 1029 break; 1030 case 1: /* audit -n */ 1031 (void) pthread_mutex_lock(&log_mutex); 1032 if (open_log(activeDir) == 1) /* ok */ 1033 openNewFile = 0; 1034 (void) pthread_mutex_unlock(&log_mutex); 1035 break; 1036 } 1037 1038 rc = AUDITD_SUCCESS; 1039 *ret_list = NULL; 1040 1041 return (rc); 1042 } 1043 1044 auditd_rc_t 1045 auditd_plugin_close(char **error) 1046 { 1047 *error = NULL; 1048 1049 (void) pthread_mutex_lock(&log_mutex); 1050 close_log(activeDir, "", ""); 1051 freedirlist(activeDir); 1052 activeDir = NULL; 1053 (void) pthread_mutex_unlock(&log_mutex); 1054 1055 DPRINT((dbfp, "binfile: closed\n")); 1056 1057 (void) __logpost(""); 1058 1059 if (binfile_is_open) { 1060 (void) pthread_mutex_destroy(&log_mutex); 1061 binfile_is_open = 0; 1062 /* LINTED */ 1063 } else { 1064 DPRINT((dbfp, 1065 "auditd_plugin_close() called when already closed.")); 1066 } 1067 am_open = 0; 1068 return (AUDITD_SUCCESS); 1069 } 1070