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) { (void) 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 (void) fprintf(dbfp, "Directory list:\n\t%s\n", 344 listhead->dl_dirname); 345 thisdir = listhead->dl_next; 346 347 while (thisdir != listhead) { 348 (void) fprintf(dbfp, "\t%s\n", thisdir->dl_dirname); 349 thisdir = thisdir->dl_next; 350 } 351 } 352 #endif /* DEBUG */ 353 thisdir = listhead; 354 /* 355 * See if the list has changed. 356 * If there was a change rc = 0 if no change, else 1 357 */ 358 rc = 0; /* no change */ 359 360 if (node_count == activeCount) { 361 n1 = listhead; 362 n2 = activeList; 363 do { 364 if (strcmp(n1->dl_dirname, n2->dl_dirname) != 0) { 365 DPRINT((dbfp, 366 "binfile: new dirname = %s\n" 367 "binfile: old dirname = %s\n", 368 n1->dl_dirname, 369 n2->dl_dirname)); 370 rc = -2; 371 break; 372 } 373 n1 = n1->dl_next; 374 n2 = n2->dl_next; 375 } while ((n1 != listhead) && (n2 != activeList)); 376 } else { 377 DPRINT((dbfp, "binfile: old dir count = %d\n" 378 "binfile: new dir count = %d\n", 379 activeCount, node_count)); 380 rc = -2; 381 } 382 if (rc == -2) { 383 (void) pthread_mutex_lock(&log_mutex); 384 DPRINT((dbfp, "loadauditlist: close / open log\n")); 385 if (open_log(listhead) == 0) { 386 openNewFile = 1; /* try again later */ 387 } else { 388 openNewFile = 0; 389 } 390 freedirlist(activeList); /* old list */ 391 activeList = listhead; /* new list */ 392 activeDir = startdir = thisdir; 393 activeCount = node_count; 394 (void) pthread_mutex_unlock(&log_mutex); 395 } else 396 freedirlist(listhead); 397 /* 398 * Get the minfree value. If minfree comes in via the attribute 399 * list, ignore the possibility it may also be listed on a separate 400 * audit_control line. 401 */ 402 if (minfreestr != NULL) 403 temp_minfree = atoi(minfreestr); 404 else if (!(_getacmin(ach, &temp_minfree) == 0)) 405 temp_minfree = 0; 406 407 if ((temp_minfree < 0) || (temp_minfree > 100)) 408 temp_minfree = 0; 409 410 if (minfree != temp_minfree) { 411 DPRINT((dbfp, "minfree: old = %d, new = %d\n", 412 minfree, temp_minfree)); 413 rc = -2; /* data change */ 414 minfree = temp_minfree; 415 } 416 _endac(ach); 417 418 return (rc); 419 } 420 421 422 /* 423 * getauditdate - get the current time (GMT) and put it in the form 424 * yyyymmddHHMMSS . 425 */ 426 static void 427 getauditdate(char *date) 428 { 429 struct timeval tp; 430 struct timezone tzp; 431 struct tm tm; 432 433 (void) gettimeofday(&tp, &tzp); 434 tm = *gmtime(&tp.tv_sec); 435 /* 436 * NOTE: if we want to use gmtime, we have to be aware that the 437 * structure only keeps the year as an offset from TM_YEAR_BASE. 438 * I have used TM_YEAR_BASE in this code so that if they change 439 * this base from 1900 to 2000, it will hopefully mean that this 440 * code does not have to change. TM_YEAR_BASE is defined in 441 * tzfile.h . 442 */ 443 (void) sprintf(date, "%.4d%.2d%.2d%.2d%.2d%.2d", 444 tm.tm_year + TM_YEAR_BASE, tm.tm_mon + 1, tm.tm_mday, 445 tm.tm_hour, tm.tm_min, tm.tm_sec); 446 } 447 448 449 450 /* 451 * write_file_token - put the file token into the audit log 452 */ 453 static int 454 write_file_token(int fd, char *name) 455 { 456 adr_t adr; /* xdr ptr */ 457 struct timeval tv; /* time now */ 458 char for_adr[AUDIT_FNAME_SZ + AUDIT_FNAME_SZ]; /* plenty of room */ 459 char token_id; 460 short i; 461 462 (void) gettimeofday(&tv, (struct timezone *)0); 463 i = strlen(name) + 1; 464 adr_start(&adr, for_adr); 465 #ifdef _LP64 466 token_id = AUT_OTHER_FILE64; 467 adr_char(&adr, &token_id, 1); 468 adr_int64(&adr, (int64_t *)& tv, 2); 469 #else 470 token_id = AUT_OTHER_FILE32; 471 adr_char(&adr, &token_id, 1); 472 adr_int32(&adr, (int32_t *)& tv, 2); 473 #endif 474 475 adr_short(&adr, &i, 1); 476 adr_char(&adr, name, i); 477 478 if (write(fd, for_adr, adr_count(&adr)) < 0) { 479 DPRINT((dbfp, "binfile: Bad write\n")); 480 return (errno); 481 } 482 return (0); 483 } 484 485 /* 486 * close_log - close the file if open. Also put the name of the 487 * new log file in the trailer, and rename the old file 488 * to oldname. The caller must hold log_mutext while calling 489 * close_log since any change to activeDir is a complete redo 490 * of all it points to. 491 * arguments - 492 * oldname - the new name for the file to be closed 493 * newname - the name of the new log file (for the trailer) 494 */ 495 static void 496 close_log(dirlist_t *currentdir, char *oname, char *newname) 497 { 498 char auditdate[AUDIT_DATE_SZ+1]; 499 char *name; 500 char oldname[AUDIT_FNAME_SZ+1]; 501 502 if ((currentdir == NULL) || (currentdir->dl_fd == -1)) 503 return; 504 /* 505 * If oldname is blank, we were called by auditd_plugin_close() 506 * instead of by open_log, so we need to update our name. 507 */ 508 (void) strlcpy(oldname, oname, AUDIT_FNAME_SZ); 509 510 if (strcmp(oldname, "") == 0) { 511 getauditdate(auditdate); 512 513 assert(currentdir->dl_filename != NULL); 514 515 (void) strlcpy(oldname, currentdir->dl_filename, 516 AUDIT_FNAME_SZ); 517 518 name = strrchr(oldname, '/') + 1; 519 (void) memcpy(name + AUDIT_DATE_SZ + 1, auditdate, 520 AUDIT_DATE_SZ); 521 } 522 /* 523 * Write the trailer record and rename and close the file. 524 * If any of the write, rename, or close fail, ignore it 525 * since there is not much else we can do and the next open() 526 * will trigger the necessary full directory logic. 527 * 528 * newname is "" if binfile is being closed down. 529 */ 530 (void) write_file_token(currentdir->dl_fd, newname); 531 if (currentdir->dl_fd >= 0) { 532 (void) fsync(currentdir->dl_fd); 533 (void) close(currentdir->dl_fd); 534 } 535 currentdir->dl_fd = -1; 536 (void) rename(currentdir->dl_filename, oldname); 537 538 DPRINT((dbfp, "binfile: Log closed %s\n", oldname)); 539 540 free(currentdir->dl_filename); 541 currentdir->dl_filename = NULL; 542 } 543 544 545 /* 546 * open_log - open a new file in the current directory. If a 547 * file is already open, close it. 548 * 549 * return 1 if ok, 0 if all directories are full. 550 * 551 * lastOpenDir - used to get the oldfile name (and change it), 552 * to close the oldfile. 553 * 554 * The caller must hold log_mutex while calling open_log. 555 * 556 */ 557 static int 558 open_log(dirlist_t *current_dir) 559 { 560 char auditdate[AUDIT_DATE_SZ + 1]; 561 char oldname[AUDIT_FNAME_SZ + 1] = ""; 562 char newname[AUDIT_FNAME_SZ + 1]; 563 char *name; /* pointer into oldname */ 564 int opened; 565 int error = 0; 566 int newfd = 0; 567 568 static char host[MAXHOSTNAMELEN + 1] = ""; 569 /* previous directory with open log file */ 570 static dirlist_t *lastOpenDir = NULL; 571 572 if (host[0] == '\0') 573 (void) gethostname(host, MAXHOSTNAMELEN); 574 575 /* Get a filename which does not already exist */ 576 opened = 0; 577 while (!opened) { 578 getauditdate(auditdate); 579 (void) snprintf(newname, AUDIT_FNAME_SZ, 580 "%s/%s.not_terminated.%s", 581 current_dir->dl_dirname, auditdate, host); 582 newfd = open(newname, 583 O_RDWR | O_APPEND | O_CREAT | O_EXCL, 0640); 584 if (newfd < 0) { 585 switch (errno) { 586 case EEXIST: 587 DPRINT((dbfp, 588 "open_log says duplicate for %s " 589 "(will try another)\n", newname)); 590 (void) sleep(1); 591 break; 592 default: 593 /* open failed */ 594 DPRINT((dbfp, 595 "open_log says full for %s: %s\n", 596 newname, strerror(errno))); 597 current_dir->dl_space = SPACE_FULL; 598 current_dir = current_dir->dl_next; 599 return (0); 600 } /* switch */ 601 } else 602 opened = 1; 603 } /* while */ 604 605 /* 606 * When we get here, we have opened our new log file. 607 * Now we need to update the name of the old file to 608 * store in this file's header. lastOpenDir may point 609 * to current_dir if the list is only one entry long and 610 * there is only one list. 611 */ 612 if ((lastOpenDir != NULL) && (lastOpenDir->dl_filename != NULL)) { 613 (void) strlcpy(oldname, lastOpenDir->dl_filename, 614 AUDIT_FNAME_SZ); 615 name = (char *)strrchr(oldname, '/') + 1; 616 617 (void) memcpy(name + AUDIT_DATE_SZ + 1, auditdate, 618 AUDIT_DATE_SZ); 619 620 close_log(lastOpenDir, oldname, newname); 621 } 622 error = write_file_token(newfd, oldname); 623 if (error) { 624 /* write token failed */ 625 (void) close(newfd); 626 627 current_dir->dl_space = SPACE_FULL; 628 current_dir->dl_fd = -1; 629 free(current_dir->dl_filename); 630 current_dir->dl_filename = NULL; 631 current_dir = current_dir->dl_next; 632 return (0); 633 } else { 634 lastOpenDir = current_dir; 635 current_dir->dl_fd = newfd; 636 current_dir->dl_filename = strdup(newname); 637 638 /* 639 * New file opened, so reset file size statistic (used 640 * to ensure audit log does not grow above size limit 641 * set by p_fsize). 642 */ 643 binfile_cursize = 0; 644 645 (void) __logpost(newname); 646 647 DPRINT((dbfp, "binfile: Log opened: %s\n", newname)); 648 return (1); 649 } 650 } 651 652 #define IGNORE_SIZE 8192 653 /* 654 * spacecheck - determine whether the given directory's filesystem 655 * has the at least the space requested. Also set the space 656 * value in the directory list structure. If the caller 657 * passes other than PLENTY_SPACE or SOFT_SPACE, the caller should 658 * ignore the return value. Otherwise, 0 = less than the 659 * requested space is available, 1 = at least the requested space 660 * is available. 661 * 662 * log_mutex must be held by the caller 663 * 664 * -1 is returned if stat fails 665 * 666 * IGNORE_SIZE is one page (Sol 9 / 10 timeframe) and is the default 667 * buffer size written for Sol 9 and earlier. To keep the same accuracy 668 * for the soft limit check as before, spacecheck checks for space 669 * remaining IGNORE_SIZE bytes. This reduces the number of statvfs() 670 * calls and related math. 671 * 672 * globals - 673 * minfree - the soft limit, i.e., the % of filesystem to reserve 674 */ 675 static int 676 spacecheck(dirlist_t *thisdir, int test_limit, size_t next_buf_size) 677 { 678 struct statvfs sb; 679 static int ignore_size = 0; 680 681 ignore_size += next_buf_size; 682 683 if ((test_limit == PLENTY_SPACE) && (ignore_size < IGNORE_SIZE)) 684 return (1); 685 686 assert(thisdir != NULL); 687 688 if (statvfs(thisdir->dl_dirname, &sb) < 0) { 689 thisdir->dl_space = SPACE_FULL; 690 minfreeblocks = AVAIL_MIN; 691 return (-1); 692 } else { 693 minfreeblocks = ((minfree * sb.f_blocks) / 100) + AVAIL_MIN; 694 695 if (sb.f_bavail < AVAIL_MIN) 696 thisdir->dl_space = SPACE_FULL; 697 else if (sb.f_bavail > minfreeblocks) { 698 thisdir->dl_space = fullness_state = PLENTY_SPACE; 699 ignore_size = 0; 700 } else 701 thisdir->dl_space = SOFT_SPACE; 702 } 703 if (thisdir->dl_space == PLENTY_SPACE) 704 return (1); 705 706 return (thisdir->dl_space == test_limit); 707 } 708 709 /* 710 * Parses p_fsize value and contains it within the range FSIZE_MIN and 711 * INT_MAX so using uints won't cause an undetected overflow of 712 * INT_MAX. Defaults to 0 if the value is invalid or is missing. 713 */ 714 static void 715 save_maxsize(char *maxsize) { 716 /* 717 * strtol() returns a long which could be larger than int so 718 * store here for sanity checking first 719 */ 720 long proposed_maxsize; 721 722 if (maxsize != NULL) { 723 /* 724 * There is no explicit error return from strtol() so 725 * we may need to depend on the value of errno. 726 */ 727 errno = 0; 728 proposed_maxsize = strtol(maxsize, (char **)NULL, 10); 729 730 /* 731 * If sizeof(long) is greater than sizeof(int) on this 732 * platform, proposed_maxsize might be greater than 733 * INT_MAX without it being reported as ERANGE. 734 */ 735 if ((errno == ERANGE) || 736 ((proposed_maxsize != 0) && 737 (proposed_maxsize < FSIZE_MIN)) || 738 (proposed_maxsize > INT_MAX)) { 739 binfile_maxsize = 0; 740 DPRINT((dbfp, "binfile: p_fsize parameter out of " 741 "range: %s\n", maxsize)); 742 /* 743 * Inform administrator of the error via 744 * syslog 745 */ 746 __audit_syslog("audit_binfile.so", 747 LOG_CONS | LOG_NDELAY, 748 LOG_DAEMON, LOG_ERR, 749 gettext("p_fsize parameter out of range\n")); 750 } else { 751 binfile_maxsize = proposed_maxsize; 752 } 753 } else { /* p_fsize string was not present */ 754 binfile_maxsize = 0; 755 } 756 757 DPRINT((dbfp, "binfile: set maxsize to %d\n", binfile_maxsize)); 758 } 759 760 /* 761 * auditd_plugin() writes a buffer to the currently open file. The 762 * global "openNewFile" is used to force a new log file for cases such 763 * as the initial open, when minfree is reached, the p_fsize value is 764 * exceeded or the current file system fills up, and "audit -s" with 765 * changed parameters. For "audit -n" a new log file is opened 766 * immediately in auditd_plugin_open(). 767 * 768 * This function manages one or more audit directories as follows: 769 * 770 * If the current open file is in a directory that has not 771 * reached the soft limit, write the input data and return. 772 * 773 * Scan the list of directories for one which has not reached 774 * the soft limit; if one is found, write and return. Such 775 * a writable directory is in "PLENTY_SPACE" state. 776 * 777 * Scan the list of directories for one which has not reached 778 * the hard limit; if one is found, write and return. This 779 * directory in in "SOFT_SPACE" state. 780 * 781 * Oh, and if a write fails, handle it like a hard space limit. 782 * 783 * audit_warn (via __audit_dowarn()) is used to alert an operator 784 * at various levels of fullness. 785 */ 786 /* ARGSUSED */ 787 auditd_rc_t 788 auditd_plugin(const char *input, size_t in_len, uint64_t sequence, char **error) 789 { 790 auditd_rc_t rc = AUDITD_FAIL; 791 int open_status; 792 size_t out_len; 793 /* avoid excess audit_warnage */ 794 static int allsoftfull_warning = 0; 795 static int allhard_pause = 0; 796 static struct timeval next_allhard; 797 struct timeval now; 798 #if DEBUG 799 int statrc; 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 (void) fprintf(dbfp, 806 "binfile: buffer sequence=%llu but prev=%llu=n", 807 sequence, last_sequence); 808 last_sequence = sequence; 809 810 (void) fprintf(dbfp, "binfile: input seq=%llu, len=%d\n", 811 sequence, in_len); 812 #endif 813 *error = NULL; 814 /* 815 * lock is for activeDir, referenced by open_log() and close_log() 816 */ 817 (void) pthread_mutex_lock(&log_mutex); 818 819 /* 820 * If this would take us over the maximum size, open a new 821 * file, unless maxsize is 0, in which case growth of the 822 * audit log is unrestricted. 823 */ 824 if ((binfile_maxsize != 0) && 825 ((binfile_cursize + in_len) > binfile_maxsize)) { 826 DPRINT((dbfp, "binfile: maxsize exceeded, opening new audit " 827 "file.\n")); 828 openNewFile = 1; 829 } 830 831 while (rc == AUDITD_FAIL) { 832 open_status = 1; 833 if (openNewFile) { 834 open_status = open_log(activeDir); 835 if (open_status == 1) /* ok */ 836 openNewFile = 0; 837 } 838 /* 839 * consider "space ok" return and error return the same; 840 * a -1 means spacecheck couldn't check for space. 841 */ 842 #if !DEBUG 843 if ((open_status == 1) && 844 (spacecheck(activeDir, fullness_state, in_len) != 0)) { 845 #else 846 if ((open_status == 1) && 847 (statrc = spacecheck(activeDir, fullness_state, 848 in_len) != 0)) { 849 DPRINT((dbfp, "binfile: returned from spacecheck\n")); 850 /* 851 * The last copy of last_file_written_to is 852 * never free'd, so there will be one open 853 * memory reference on exit. It's debug only. 854 */ 855 if ((last_file_written_to != NULL) && 856 (strcmp(last_file_written_to, 857 activeDir->dl_filename) != 0)) { 858 DPRINT((dbfp, "binfile: now writing to %s\n", 859 activeDir->dl_filename)); 860 free(last_file_written_to); 861 } 862 DPRINT((dbfp, "binfile: finished some debug stuff\n")); 863 last_file_written_to = 864 strdup(activeDir->dl_filename); 865 #endif 866 out_len = write(activeDir->dl_fd, input, in_len); 867 DPRINT((dbfp, "binfile: finished the write\n")); 868 869 binfile_cursize += out_len; 870 871 if (out_len == in_len) { 872 DPRINT((dbfp, 873 "binfile: write_count=%llu, sequence=%llu," 874 " l=%u\n", 875 ++write_count, sequence, out_len)); 876 allsoftfull_warning = 0; 877 activeDir->dl_flags = 0; 878 879 rc = AUDITD_SUCCESS; 880 break; 881 } else if (!(activeDir->dl_flags & HARD_WARNED)) { 882 DPRINT((dbfp, 883 "binfile: write failed, sequence=%llu, " 884 "l=%u\n", sequence, out_len)); 885 DPRINT((dbfp, "hard warning sent.\n")); 886 __audit_dowarn("hard", activeDir->dl_dirname, 887 0); 888 889 activeDir->dl_flags |= HARD_WARNED; 890 } 891 } else { 892 DPRINT((dbfp, "binfile: statrc=%d, fullness_state=%d\n", 893 statrc, fullness_state)); 894 if (!(activeDir->dl_flags & SOFT_WARNED) && 895 (activeDir->dl_space == SOFT_SPACE)) { 896 DPRINT((dbfp, "soft warning sent\n")); 897 __audit_dowarn("soft", 898 activeDir->dl_dirname, 0); 899 activeDir->dl_flags |= SOFT_WARNED; 900 } 901 if (!(activeDir->dl_flags & HARD_WARNED) && 902 (activeDir->dl_space == SPACE_FULL)) { 903 DPRINT((dbfp, "hard warning sent.\n")); 904 __audit_dowarn("hard", 905 activeDir->dl_dirname, 0); 906 activeDir->dl_flags |= HARD_WARNED; 907 } 908 } 909 DPRINT((dbfp, "binfile: activeDir=%s, next=%s\n", 910 activeDir->dl_dirname, activeDir->dl_next->dl_dirname)); 911 912 activeDir = activeDir->dl_next; 913 openNewFile = 1; 914 915 if (activeDir == startdir) { /* full circle */ 916 if (fullness_state == PLENTY_SPACE) { /* once */ 917 fullness_state = SOFT_SPACE; 918 if (allsoftfull_warning == 0) { 919 allsoftfull_warning++; 920 __audit_dowarn("allsoft", "", 0); 921 } 922 } else { /* full circle twice */ 923 if ((hung_count > 0) && !allhard_pause) { 924 allhard_pause = 1; 925 (void) gettimeofday(&next_allhard, 926 NULL); 927 next_allhard.tv_sec += ALLHARD_DELAY; 928 } 929 930 if (allhard_pause) { 931 (void) gettimeofday(&now, NULL); 932 if (now.tv_sec >= next_allhard.tv_sec) { 933 allhard_pause = 0; 934 __audit_dowarn("allhard", "", 935 ++hung_count); 936 } 937 } else { 938 __audit_dowarn("allhard", "", 939 ++hung_count); 940 } 941 minfreeblocks = AVAIL_MIN; 942 rc = AUDITD_RETRY; 943 *error = strdup(gettext( 944 "all partitions full\n")); 945 (void) __logpost(""); 946 } 947 } 948 } 949 (void) pthread_mutex_unlock(&log_mutex); 950 951 return (rc); 952 } 953 954 955 /* 956 * the open function uses getacdir() and getacmin to determine which 957 * directories to use and when to switch. It takes no inputs. 958 * 959 * It may be called multiple times as auditd handles SIGHUP and SIGUSR1 960 * corresponding to the audit(1M) flags -s and -n 961 * 962 * kvlist is NULL only if auditd caught a SIGUSR1, so after the first 963 * time open is called, the reason is -s if kvlist != NULL and -n 964 * otherwise. 965 * 966 */ 967 968 auditd_rc_t 969 auditd_plugin_open(const kva_t *kvlist, char **ret_list, char **error) 970 { 971 int rc = 0; 972 int status; 973 int reason; 974 char *dirlist; 975 char *minfree; 976 char *maxsize; 977 kva_t *kv; 978 979 *error = NULL; 980 *ret_list = NULL; 981 kv = (kva_t *)kvlist; 982 983 if (am_open) { 984 if (kvlist == NULL) 985 reason = 1; /* audit -n */ 986 else 987 reason = 2; /* audit -s */ 988 } else { 989 reason = 0; /* initial open */ 990 #if DEBUG 991 dbfp = __auditd_debug_file_open(); 992 #endif 993 } 994 DPRINT((dbfp, "binfile: am_open=%d, reason=%d\n", am_open, reason)); 995 996 am_open = 1; 997 998 if (kvlist == NULL) { 999 dirlist = NULL; 1000 minfree = NULL; 1001 maxsize = NULL; 1002 } else { 1003 dirlist = kva_match(kv, "p_dir"); 1004 minfree = kva_match(kv, "p_minfree"); 1005 maxsize = kva_match(kv, "p_fsize"); 1006 } 1007 switch (reason) { 1008 case 0: /* initial open */ 1009 if (!binfile_is_open) 1010 (void) pthread_mutex_init(&log_mutex, NULL); 1011 binfile_is_open = 1; 1012 openNewFile = 1; 1013 /* FALLTHRU */ 1014 case 2: /* audit -s */ 1015 /* handle p_fsize parameter */ 1016 save_maxsize(maxsize); 1017 1018 fullness_state = PLENTY_SPACE; 1019 status = loadauditlist(dirlist, minfree); 1020 1021 if (status == -1) { 1022 (void) __logpost(""); 1023 *error = strdup(gettext("no directories configured")); 1024 return (AUDITD_RETRY); 1025 } else if (status == AUDITD_NO_MEMORY) { 1026 (void) __logpost(""); 1027 *error = strdup(gettext("no memory")); 1028 return (status); 1029 } else { /* status is 0 or -2 (no change or changed) */ 1030 hung_count = 0; 1031 DPRINT((dbfp, "binfile: loadauditlist returned %d\n", 1032 status)); 1033 } 1034 break; 1035 case 1: /* audit -n */ 1036 (void) pthread_mutex_lock(&log_mutex); 1037 if (open_log(activeDir) == 1) /* ok */ 1038 openNewFile = 0; 1039 (void) pthread_mutex_unlock(&log_mutex); 1040 break; 1041 } 1042 1043 rc = AUDITD_SUCCESS; 1044 *ret_list = NULL; 1045 1046 return (rc); 1047 } 1048 1049 auditd_rc_t 1050 auditd_plugin_close(char **error) 1051 { 1052 *error = NULL; 1053 1054 (void) pthread_mutex_lock(&log_mutex); 1055 close_log(activeDir, "", ""); 1056 freedirlist(activeDir); 1057 activeDir = NULL; 1058 (void) pthread_mutex_unlock(&log_mutex); 1059 1060 DPRINT((dbfp, "binfile: closed\n")); 1061 1062 (void) __logpost(""); 1063 1064 if (binfile_is_open) { 1065 (void) pthread_mutex_destroy(&log_mutex); 1066 binfile_is_open = 0; 1067 #if DEBUG 1068 } else { 1069 (void) fprintf(dbfp, 1070 "auditd_plugin_close() called when already closed."); 1071 #endif 1072 } 1073 am_open = 0; 1074 return (AUDITD_SUCCESS); 1075 } 1076