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