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