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