2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A *
2N/A * write binary audit records directly to a file.
2N/A */
2N/A
2N/A#define DEBUG 0
2N/A
2N/A#if DEBUG
2N/A#define DPRINT(x) { (void) fprintf x; }
2N/A#else
2N/A#define DPRINT(x)
2N/A#endif
2N/A
2N/A/*
2N/A * auditd_plugin_open(), auditd_plugin() and auditd_plugin_close()
2N/A * implement a replacable library for use by auditd; they are a
2N/A * project private interface and may change without notice.
2N/A *
2N/A */
2N/A
2N/A#include <assert.h>
2N/A#include <bsm/audit.h>
2N/A#include <bsm/audit_record.h>
2N/A#include <bsm/libbsm.h>
2N/A#include <errno.h>
2N/A#include <fcntl.h>
2N/A#include <libintl.h>
2N/A#include <netdb.h>
2N/A#include <pthread.h>
2N/A#include <secdb.h>
2N/A#include <signal.h>
2N/A#include <stdio.h>
2N/A#include <stdlib.h>
2N/A#include <string.h>
2N/A#include <sys/param.h>
2N/A#include <sys/types.h>
2N/A#include <time.h>
2N/A#include <tzfile.h>
2N/A#include <unistd.h>
2N/A#include <sys/vfs.h>
2N/A#include <limits.h>
2N/A#include <syslog.h>
2N/A#include <security/auditd.h>
2N/A#include <audit_plugin.h>
2N/A
2N/A/* gettext() obfuscation routine for lint */
2N/A#ifdef __lint
2N/A#define gettext(x) x
2N/A#endif
2N/A
2N/A#define AUDIT_DATE_SZ 14
2N/A#define AUDIT_FNAME_SZ 2 * AUDIT_DATE_SZ + 2 + MAXHOSTNAMELEN
2N/A
2N/A /* per-directory status */
2N/A#define SOFT_SPACE 0 /* minfree or less space available */
2N/A#define PLENTY_SPACE 1 /* more than minfree available */
2N/A#define SPACE_FULL 2 /* out of space */
2N/A
2N/A#define AVAIL_MIN 50 /* If there are less that this number */
2N/A /* of blocks avail, the filesystem is */
2N/A /* presumed full. */
2N/A
2N/A#define ALLHARD_DELAY 20 /* Call audit_warn(allhard) every 20 seconds */
2N/A
2N/A/* minimum reasonable size in bytes to roll over an audit file */
2N/A#define FSIZE_MIN 512000
2N/A
2N/A/*
2N/A * The directory list is a circular linked list. It is pointed into by
2N/A * activeDir. Each element contains the pointer to the next
2N/A * element, the directory pathname, a flag for how much space there is
2N/A * in the directory's filesystem, and a file handle. Since a new
2N/A * directory list can be created from auditd_plugin_open() while the
2N/A * current list is in use, activeDir is protected by log_mutex.
2N/A */
2N/Atypedef struct dirlist_s dirlist_t;
2N/Astruct dirlist_s {
2N/A dirlist_t *dl_next;
2N/A int dl_space;
2N/A int dl_flags;
2N/A char *dl_dirname;
2N/A char *dl_filename; /* file name (not path) if open */
2N/A int dl_fd; /* file handle, -1 unless open */
2N/A};
2N/A/*
2N/A * Defines for dl_flags
2N/A */
2N/A#define SOFT_WARNED 0x0001 /* already did soft warning for this dir */
2N/A#define HARD_WARNED 0x0002 /* already did hard warning for this dir */
2N/A
2N/Astruct plg_ctrl {
2N/A#if DEBUG
2N/A FILE *dbfp; /* debug file */
2N/A char *last_file_written_to; /* last audit log file */
2N/A uint64_t last_sequence;
2N/A uint64_t write_count;
2N/A#endif
2N/A char host[MAXHOSTNAMELEN + 1];
2N/A
2N/A pthread_mutex_t log_mutex; /* protects activeDir */
2N/A boolean_t binfile_is_open;
2N/A
2N/A int minfree;
2N/A int minfreeblocks; /* minfree in blocks */
2N/A
2N/A dirlist_t *activeList; /* directory list */
2N/A dirlist_t *lastOpenDir; /* last activeDir */
2N/A dirlist_t *activeDir; /* to be current directory */
2N/A dirlist_t *startdir; /* first dir in the ring */
2N/A int activeCount; /* number of dirs in the ring */
2N/A
2N/A boolean_t openNewFile; /* need to open a new file */
2N/A int hung_count; /* count of audit_warn hard */
2N/A
2N/A /* flag from audit_plugin_open to audit_plugin_close */
2N/A boolean_t am_open;
2N/A int fullness_state; /* preferred dir state */
2N/A
2N/A /*
2N/A * These are used to implement a maximum size for the auditing
2N/A * file. binfile_maxsize is set via the 'p_fsize' parameter to the
2N/A * audit_binfile plugin.
2N/A */
2N/A uint64_t binfile_cursize;
2N/A uint64_t binfile_maxsize;
2N/A int ignore_size;
2N/A
2N/A /* avoid excess audit_warnage */
2N/A int allsoftfull_warning;
2N/A int allhard_pause;
2N/A struct timeval next_allhard;
2N/A};
2N/Atypedef struct plg_ctrl plg_ctrl_t;
2N/A
2N/Aboolean_t
2N/Ainit_ctrls(plg_ctrl_t **plg_ctrl)
2N/A{
2N/A if ((*plg_ctrl = calloc(1, sizeof (plg_ctrl_t))) == NULL) {
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A (*plg_ctrl)->minfree = -1;
2N/A (*plg_ctrl)->fullness_state = PLENTY_SPACE;
2N/A (*plg_ctrl)->am_open = B_FALSE;
2N/A
2N/A return (B_TRUE);
2N/A}
2N/A
2N/Astatic int open_log(dirlist_t *, plg_ctrl_t *);
2N/A
2N/Astatic void
2N/Afreedirlist(dirlist_t *head)
2N/A{
2N/A dirlist_t *n1, *n2;
2N/A /*
2N/A * Free up the old directory list if any
2N/A */
2N/A if (head != NULL) {
2N/A n1 = head;
2N/A do {
2N/A n2 = n1->dl_next;
2N/A free(n1->dl_dirname);
2N/A free(n1->dl_filename);
2N/A free(n1);
2N/A n1 = n2;
2N/A } while (n1 != head);
2N/A }
2N/A}
2N/A
2N/Adirlist_t *
2N/Adupdirnode(dirlist_t *node_orig)
2N/A{
2N/A dirlist_t *node_new;
2N/A
2N/A if ((node_new = calloc(1, sizeof (dirlist_t))) == NULL) {
2N/A return (NULL);
2N/A }
2N/A
2N/A if (node_orig->dl_dirname != NULL &&
2N/A (node_new->dl_dirname = strdup(node_orig->dl_dirname)) == NULL ||
2N/A node_orig->dl_filename != NULL &&
2N/A (node_new->dl_filename = strdup(node_orig->dl_filename)) == NULL) {
2N/A freedirlist(node_new);
2N/A return (NULL);
2N/A }
2N/A
2N/A node_new->dl_next = node_new;
2N/A node_new->dl_space = node_orig->dl_space;
2N/A node_new->dl_flags = node_orig->dl_flags;
2N/A node_new->dl_fd = node_orig->dl_fd;
2N/A
2N/A return (node_new);
2N/A}
2N/A
2N/A/*
2N/A * add to a linked list of directories available for writing
2N/A *
2N/A */
2N/A/* ARGSUSED */
2N/Astatic int
2N/Agrowauditlist(dirlist_t **listhead, char *dirlist, dirlist_t *endnode,
2N/A int *count, plg_ctrl_t *ctrl)
2N/A{
2N/A dirlist_t *node;
2N/A char *bs, *be;
2N/A dirlist_t **node_p;
2N/A char *dirname;
2N/A char *remainder;
2N/A
2N/A DPRINT((ctrl->dbfp, "binfile: dirlist=%s\n", dirlist));
2N/A
2N/A if (*listhead == NULL)
2N/A node_p = listhead;
2N/A else
2N/A node_p = &(endnode->dl_next);
2N/A
2N/A node = NULL;
2N/A while ((dirname = strtok_r(dirlist, ",", &remainder)) != NULL) {
2N/A dirlist = NULL;
2N/A
2N/A DPRINT((ctrl->dbfp, "binfile: p_dir = %s\n", dirname));
2N/A
2N/A (*count)++;
2N/A node = malloc(sizeof (dirlist_t));
2N/A if (node == NULL)
2N/A return (AUDITD_NO_MEMORY);
2N/A
2N/A node->dl_flags = 0;
2N/A node->dl_filename = NULL;
2N/A node->dl_fd = -1;
2N/A node->dl_space = PLENTY_SPACE;
2N/A
2N/A node->dl_dirname = malloc((unsigned)strlen(dirname) + 1);
2N/A if (node->dl_dirname == NULL) {
2N/A free(node);
2N/A return (AUDITD_NO_MEMORY);
2N/A }
2N/A
2N/A bs = dirname;
2N/A while ((*bs == ' ') || (*bs == '\t')) /* trim blanks */
2N/A bs++;
2N/A be = bs + strlen(bs) - 1;
2N/A while (be > bs) { /* trim trailing blanks */
2N/A if ((*bs != ' ') && (*bs != '\t'))
2N/A break;
2N/A be--;
2N/A }
2N/A *(be + 1) = '\0';
2N/A (void) strlcpy(node->dl_dirname, bs, AUDIT_FNAME_SZ);
2N/A
2N/A if (*listhead != NULL)
2N/A node->dl_next = *listhead;
2N/A else
2N/A node->dl_next = node;
2N/A *node_p = node;
2N/A node_p = &(node->dl_next);
2N/A
2N/A }
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * create a linked list of directories available for writing
2N/A *
2N/A * if a list already exists, the two are compared and the new one is
2N/A * used only if it is different than the old.
2N/A *
2N/A * returns -2 for new or changed list, 0 for unchanged list and -1 for
2N/A * error. (Positive returns are for AUDITD_<error code> values)
2N/A *
2N/A */
2N/Astatic int
2N/Aloadauditlist(char *dirstr, char *minfreestr, plg_ctrl_t *ctrl)
2N/A{
2N/A dirlist_t *n1, *n2;
2N/A dirlist_t *listhead = NULL;
2N/A dirlist_t *thisdir;
2N/A int node_count = 0;
2N/A int rc;
2N/A int temp_minfree;
2N/A
2N/A DPRINT((ctrl->dbfp, "binfile: Loading audit list from audit service "
2N/A "(audit_binfile)\n"));
2N/A
2N/A if (dirstr == NULL || minfreestr == NULL) {
2N/A DPRINT((ctrl->dbfp, "binfile: internal error"));
2N/A return (-1);
2N/A }
2N/A if ((rc = growauditlist(&listhead, dirstr, NULL, &node_count, ctrl))
2N/A != 0) {
2N/A return (rc);
2N/A }
2N/A if (node_count == 0) {
2N/A /*
2N/A * there was a problem getting the directory
2N/A * list or remote host info from the audit_binfile
2N/A * configuration even though auditd thought there was
2N/A * at least 1 good entry
2N/A */
2N/A DPRINT((ctrl->dbfp, "binfile: "
2N/A "problem getting directory / libpath list "
2N/A "from audit_binfile configuration.\n"));
2N/A return (-1);
2N/A }
2N/A
2N/A#if DEBUG
2N/A /* print out directory list */
2N/A if (listhead != NULL) {
2N/A (void) fprintf(ctrl->dbfp, "Directory list:\n\t%s\n",
2N/A listhead->dl_dirname);
2N/A thisdir = listhead->dl_next;
2N/A
2N/A while (thisdir != listhead) {
2N/A (void) fprintf(ctrl->dbfp,
2N/A "\t%s\n", thisdir->dl_dirname);
2N/A thisdir = thisdir->dl_next;
2N/A }
2N/A }
2N/A#endif /* DEBUG */
2N/A
2N/A thisdir = listhead;
2N/A
2N/A /* See if the list has changed (rc = 0 if no change, else 1) */
2N/A rc = 0;
2N/A if (node_count == ctrl->activeCount) {
2N/A n1 = listhead;
2N/A n2 = ctrl->activeList;
2N/A do {
2N/A if (strcmp(n1->dl_dirname, n2->dl_dirname) != 0) {
2N/A DPRINT((ctrl->dbfp,
2N/A "binfile: new dirname = %s\n"
2N/A "binfile: old dirname = %s\n",
2N/A n1->dl_dirname,
2N/A n2->dl_dirname));
2N/A rc = -2;
2N/A break;
2N/A }
2N/A n1 = n1->dl_next;
2N/A n2 = n2->dl_next;
2N/A } while ((n1 != listhead) && (n2 != ctrl->activeList));
2N/A } else {
2N/A DPRINT((ctrl->dbfp, "binfile: dir counts differs\n"
2N/A "binfile: old dir count = %d\n"
2N/A "binfile: new dir count = %d\n",
2N/A ctrl->activeCount, node_count));
2N/A rc = -2;
2N/A }
2N/A if (rc == -2) {
2N/A (void) pthread_mutex_lock(&(ctrl->log_mutex));
2N/A DPRINT((ctrl->dbfp,
2N/A "loadauditlist: close / open audit.log(4)\n"));
2N/A if (open_log(listhead, ctrl) == 0) {
2N/A ctrl->openNewFile = B_TRUE; /* try again later */
2N/A } else {
2N/A ctrl->openNewFile = B_FALSE;
2N/A }
2N/A freedirlist(ctrl->activeList); /* old list */
2N/A ctrl->activeList = listhead; /* new list */
2N/A ctrl->startdir = thisdir;
2N/A ctrl->activeDir = thisdir;
2N/A ctrl->activeCount = node_count;
2N/A (void) pthread_mutex_unlock(&(ctrl->log_mutex));
2N/A } else {
2N/A freedirlist(listhead);
2N/A }
2N/A
2N/A /* Get the minfree value. */
2N/A if (minfreestr != NULL)
2N/A temp_minfree = atoi(minfreestr);
2N/A
2N/A if ((temp_minfree < 0) || (temp_minfree > 100))
2N/A temp_minfree = 0;
2N/A
2N/A if (ctrl->minfree != temp_minfree) {
2N/A DPRINT((ctrl->dbfp, "minfree: old = %d, new = %d\n",
2N/A ctrl->minfree, temp_minfree));
2N/A rc = -2; /* data change */
2N/A ctrl->minfree = temp_minfree;
2N/A }
2N/A
2N/A return (rc);
2N/A}
2N/A
2N/A/*
2N/A * getauditdate - get the current time (GMT) and put it in the form
2N/A * yyyymmddHHMMSS .
2N/A */
2N/Astatic void
2N/Agetauditdate(char *date)
2N/A{
2N/A struct timeval tp;
2N/A struct timezone tzp;
2N/A struct tm tm;
2N/A
2N/A (void) gettimeofday(&tp, &tzp);
2N/A tm = *gmtime(&tp.tv_sec);
2N/A /*
2N/A * NOTE: if we want to use gmtime, we have to be aware that the
2N/A * structure only keeps the year as an offset from TM_YEAR_BASE.
2N/A * I have used TM_YEAR_BASE in this code so that if they change
2N/A * this base from 1900 to 2000, it will hopefully mean that this
2N/A * code does not have to change. TM_YEAR_BASE is defined in
2N/A * tzfile.h .
2N/A */
2N/A (void) sprintf(date, "%.4d%.2d%.2d%.2d%.2d%.2d",
2N/A tm.tm_year + TM_YEAR_BASE, tm.tm_mon + 1, tm.tm_mday,
2N/A tm.tm_hour, tm.tm_min, tm.tm_sec);
2N/A}
2N/A
2N/A
2N/A
2N/A/*
2N/A * write_file_token - put the file token into the audit log
2N/A */
2N/A/* ARGSUSED */
2N/Astatic int
2N/Awrite_file_token(int fd, char *name, plg_ctrl_t *ctrl)
2N/A{
2N/A adr_t adr; /* xdr ptr */
2N/A struct timeval tv; /* time now */
2N/A char for_adr[AUDIT_FNAME_SZ + AUDIT_FNAME_SZ]; /* plenty of room */
2N/A char token_id;
2N/A short i;
2N/A
2N/A (void) gettimeofday(&tv, (struct timezone *)0);
2N/A i = strlen(name) + 1;
2N/A adr_start(&adr, for_adr);
2N/A#ifdef _LP64
2N/A token_id = AUT_OTHER_FILE64;
2N/A adr_char(&adr, &token_id, 1);
2N/A adr_int64(&adr, (int64_t *)& tv, 2);
2N/A#else
2N/A token_id = AUT_OTHER_FILE32;
2N/A adr_char(&adr, &token_id, 1);
2N/A adr_int32(&adr, (int32_t *)& tv, 2);
2N/A#endif
2N/A
2N/A adr_short(&adr, &i, 1);
2N/A adr_char(&adr, name, i);
2N/A
2N/A if (write(fd, for_adr, adr_count(&adr)) < 0) {
2N/A DPRINT((ctrl->dbfp, "binfile: Bad write\n"));
2N/A return (errno);
2N/A }
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * close_log - close the file if open. Also put the name of the
2N/A * new log file in the trailer, and rename the old file
2N/A * to oldname. The caller must hold log_mutext while calling
2N/A * close_log since any change to activeDir is a complete redo
2N/A * of all it points to.
2N/A * arguments -
2N/A * oldname - the new name for the file to be closed
2N/A * newname - the name of the new log file (for the trailer)
2N/A */
2N/Astatic void
2N/Aclose_log(dirlist_t **lastOpenDir_ptr, char *oname, char *newname,
2N/A plg_ctrl_t *ctrl)
2N/A{
2N/A char auditdate[AUDIT_DATE_SZ+1];
2N/A char *name;
2N/A char oldname[AUDIT_FNAME_SZ+1];
2N/A dirlist_t *currentdir = *lastOpenDir_ptr;
2N/A
2N/A if ((currentdir == NULL) || (currentdir->dl_fd == -1))
2N/A return;
2N/A /*
2N/A * If oldname is blank, we were called by auditd_plugin_close()
2N/A * instead of by open_log, so we need to update our name.
2N/A */
2N/A (void) strlcpy(oldname, oname, AUDIT_FNAME_SZ);
2N/A
2N/A if (strcmp(oldname, "") == 0) {
2N/A getauditdate(auditdate);
2N/A
2N/A assert(currentdir->dl_filename != NULL);
2N/A
2N/A (void) strlcpy(oldname, currentdir->dl_filename,
2N/A AUDIT_FNAME_SZ);
2N/A
2N/A name = strrchr(oldname, '/') + 1;
2N/A (void) memcpy(name + AUDIT_DATE_SZ + 1, auditdate,
2N/A AUDIT_DATE_SZ);
2N/A }
2N/A /*
2N/A * Write the trailer record and rename and close the file.
2N/A * If any of the write, rename, or close fail, ignore it
2N/A * since there is not much else we can do and the next open()
2N/A * will trigger the necessary full directory logic.
2N/A *
2N/A * newname is "" if binfile is being closed down.
2N/A */
2N/A (void) write_file_token(currentdir->dl_fd, newname, ctrl);
2N/A if (currentdir->dl_fd >= 0) {
2N/A (void) fsync(currentdir->dl_fd);
2N/A (void) close(currentdir->dl_fd);
2N/A }
2N/A currentdir->dl_fd = -1;
2N/A (void) rename(currentdir->dl_filename, oldname);
2N/A
2N/A DPRINT((ctrl->dbfp, "binfile: Log closed %s\n", oldname));
2N/A
2N/A freedirlist(currentdir);
2N/A *lastOpenDir_ptr = NULL;
2N/A}
2N/A
2N/A
2N/A/*
2N/A * open_log - open a new file in the current directory. If a
2N/A * file is already open, close it.
2N/A *
2N/A * return 1 if ok, 0 if all directories are full.
2N/A *
2N/A * lastOpenDir - used to get the oldfile name (and change it),
2N/A * to close the oldfile.
2N/A *
2N/A * The caller must hold log_mutex while calling open_log.
2N/A *
2N/A */
2N/Astatic int
2N/Aopen_log(dirlist_t *current_dir, plg_ctrl_t *ctrl)
2N/A{
2N/A char auditdate[AUDIT_DATE_SZ + 1];
2N/A char oldname[AUDIT_FNAME_SZ + 1] = "";
2N/A char newname[AUDIT_FNAME_SZ + 1];
2N/A char *name; /* pointer into oldname */
2N/A int opened = 0;
2N/A int error = 0;
2N/A int newfd = 0;
2N/A
2N/A /* previous directory with open log file */
2N/A
2N/A if (ctrl->host[0] == '\0')
2N/A (void) gethostname(ctrl->host, MAXHOSTNAMELEN);
2N/A
2N/A /* Get a filename which does not already exist */
2N/A while (!opened) {
2N/A getauditdate(auditdate);
2N/A (void) snprintf(newname, AUDIT_FNAME_SZ,
2N/A "%s/%s.not_terminated.%s",
2N/A current_dir->dl_dirname, auditdate, ctrl->host);
2N/A newfd = open(newname,
2N/A O_RDWR | O_APPEND | O_CREAT | O_EXCL, 0640);
2N/A if (newfd < 0) {
2N/A switch (errno) {
2N/A case EEXIST:
2N/A DPRINT((ctrl->dbfp,
2N/A "open_log says duplicate for %s "
2N/A "(will try another)\n", newname));
2N/A (void) sleep(1);
2N/A break;
2N/A case ENOENT: {
2N/A /* invalid path */
2N/A DPRINT((ctrl->dbfp,
2N/A "open_log says about %s: %s\n",
2N/A newname, strerror(errno)));
2N/A __audit_syslog("audit_binfile.so",
2N/A LOG_CONS | LOG_NDELAY,
2N/A LOG_DAEMON, LOG_ERR, "No such p_dir: %s",
2N/A current_dir->dl_dirname);
2N/A current_dir = current_dir->dl_next;
2N/A return (0);
2N/A }
2N/A default:
2N/A /* open failed */
2N/A DPRINT((ctrl->dbfp,
2N/A "open_log says full for %s: %s\n",
2N/A newname, strerror(errno)));
2N/A current_dir->dl_space = SPACE_FULL;
2N/A current_dir = current_dir->dl_next;
2N/A return (0);
2N/A } /* switch */
2N/A } else
2N/A opened = 1;
2N/A } /* while */
2N/A
2N/A /*
2N/A * When we get here, we have opened our new log file.
2N/A * Now we need to update the name of the old file to
2N/A * store in this file's header. lastOpenDir may point
2N/A * to current_dir if the list is only one entry long and
2N/A * there is only one list.
2N/A */
2N/A if ((ctrl->lastOpenDir != NULL) &&
2N/A (ctrl->lastOpenDir->dl_filename != NULL)) {
2N/A (void) strlcpy(oldname, ctrl->lastOpenDir->dl_filename,
2N/A AUDIT_FNAME_SZ);
2N/A name = (char *)strrchr(oldname, '/') + 1;
2N/A
2N/A (void) memcpy(name + AUDIT_DATE_SZ + 1, auditdate,
2N/A AUDIT_DATE_SZ);
2N/A
2N/A close_log(&(ctrl->lastOpenDir), oldname, newname, ctrl);
2N/A }
2N/A error = write_file_token(newfd, oldname, ctrl);
2N/A if (error) {
2N/A /* write token failed */
2N/A (void) close(newfd);
2N/A
2N/A current_dir->dl_space = SPACE_FULL;
2N/A current_dir->dl_fd = -1;
2N/A free(current_dir->dl_filename);
2N/A current_dir->dl_filename = NULL;
2N/A current_dir = current_dir->dl_next;
2N/A return (0);
2N/A } else {
2N/A if (current_dir->dl_filename != NULL) {
2N/A free(current_dir->dl_filename);
2N/A }
2N/A current_dir->dl_filename = strdup(newname);
2N/A current_dir->dl_fd = newfd;
2N/A
2N/A if (ctrl->lastOpenDir == NULL) {
2N/A freedirlist(ctrl->lastOpenDir);
2N/A ctrl->lastOpenDir = dupdirnode(current_dir);
2N/A if (ctrl->lastOpenDir == NULL) {
2N/A __audit_syslog("audit_binfile.so",
2N/A LOG_CONS | LOG_NDELAY,
2N/A LOG_DAEMON, LOG_ERR, "no memory");
2N/A return (0);
2N/A }
2N/A DPRINT((ctrl->dbfp, "open_log created new lastOpenDir "
2N/A "(%s, %s [fd: %d])\n",
2N/A ctrl->lastOpenDir->dl_dirname == NULL ? "" :
2N/A ctrl->lastOpenDir->dl_dirname,
2N/A ctrl->lastOpenDir->dl_filename == NULL ? "" :
2N/A ctrl->lastOpenDir->dl_filename,
2N/A ctrl->lastOpenDir->dl_fd));
2N/A }
2N/A
2N/A /*
2N/A * New file opened, so reset file size statistic (used
2N/A * to ensure audit log does not grow above size limit
2N/A * set by p_fsize).
2N/A */
2N/A ctrl->binfile_cursize = 0;
2N/A
2N/A DPRINT((ctrl->dbfp, "binfile: Log opened: %s\n", newname));
2N/A return (1);
2N/A }
2N/A}
2N/A
2N/A#define IGNORE_SIZE 8192
2N/A/*
2N/A * spacecheck - determine whether the given directory's filesystem
2N/A * has the at least the space requested. Also set the space
2N/A * value in the directory list structure. If the caller
2N/A * passes other than PLENTY_SPACE or SOFT_SPACE, the caller should
2N/A * ignore the return value. Otherwise, 0 = less than the
2N/A * requested space is available, 1 = at least the requested space
2N/A * is available.
2N/A *
2N/A * log_mutex must be held by the caller
2N/A *
2N/A * -1 is returned if stat fails
2N/A *
2N/A * IGNORE_SIZE is one page (Sol 9 / 10 timeframe) and is the default
2N/A * buffer size written for Sol 9 and earlier. To keep the same accuracy
2N/A * for the soft limit check as before, spacecheck checks for space
2N/A * remaining IGNORE_SIZE bytes. This reduces the number of statvfs()
2N/A * calls and related math.
2N/A *
2N/A * globals -
2N/A * minfree - the soft limit, i.e., the % of filesystem to reserve
2N/A */
2N/Astatic int
2N/Aspacecheck(dirlist_t *thisdir, int test_limit, size_t next_buf_size,
2N/A plg_ctrl_t *ctrl)
2N/A{
2N/A struct statvfs sb;
2N/A
2N/A ctrl->ignore_size += next_buf_size;
2N/A
2N/A if ((test_limit == PLENTY_SPACE) && (ctrl->ignore_size < IGNORE_SIZE))
2N/A return (1);
2N/A
2N/A assert(thisdir != NULL);
2N/A
2N/A if (statvfs(thisdir->dl_dirname, &sb) < 0) {
2N/A thisdir->dl_space = SPACE_FULL;
2N/A ctrl->minfreeblocks = AVAIL_MIN;
2N/A return (-1);
2N/A } else {
2N/A ctrl->minfreeblocks =
2N/A ((ctrl->minfree * sb.f_blocks) / 100) + AVAIL_MIN;
2N/A
2N/A if (sb.f_bavail < AVAIL_MIN)
2N/A thisdir->dl_space = SPACE_FULL;
2N/A else if (sb.f_bavail > ctrl->minfreeblocks) {
2N/A thisdir->dl_space = ctrl->fullness_state = PLENTY_SPACE;
2N/A ctrl->ignore_size = 0;
2N/A } else
2N/A thisdir->dl_space = SOFT_SPACE;
2N/A }
2N/A if (thisdir->dl_space == PLENTY_SPACE)
2N/A return (1);
2N/A
2N/A return (thisdir->dl_space == test_limit);
2N/A}
2N/A
2N/A/*
2N/A * Parses p_fsize and checks the value for minimal size higher than FSIZE_MIN.
2N/A * Defaults to 0 (unchanged value) if the value is invalid or missing.
2N/A */
2N/Astatic void
2N/Asave_maxsize(char *maxsize, plg_ctrl_t *ctrl)
2N/A{
2N/A uint64_t proposed_maxsize;
2N/A
2N/A if (maxsize != NULL) {
2N/A if (!__audit_hrstrtonum(maxsize, &proposed_maxsize)) {
2N/A __audit_syslog("audit_binfile.so",
2N/A LOG_CONS | LOG_NDELAY, LOG_DAEMON, LOG_ERR,
2N/A "p_fsize parameter has invalid format (%s)",
2N/A maxsize);
2N/A } else if (proposed_maxsize != 0 &&
2N/A proposed_maxsize < FSIZE_MIN) {
2N/A ctrl->binfile_maxsize = 0;
2N/A DPRINT((ctrl->dbfp, "binfile: p_fsize parameter out of "
2N/A "range: %s\n", maxsize));
2N/A __audit_syslog("audit_binfile.so",
2N/A LOG_CONS | LOG_NDELAY, LOG_DAEMON, LOG_ERR,
2N/A "p_fsize parameter out of range (%s)", maxsize);
2N/A } else {
2N/A ctrl->binfile_maxsize = proposed_maxsize;
2N/A }
2N/A } else { /* maxsize string not provided */
2N/A ctrl->binfile_maxsize = 0;
2N/A }
2N/A
2N/A DPRINT((ctrl->dbfp, "binfile: set maxsize to %llu\n", binfile_maxsize));
2N/A}
2N/A
2N/A/*
2N/A * auditd_plugin() writes a buffer to the currently open file. The
2N/A * global "openNewFile" is used to force a new log file for cases such
2N/A * as the initial open, when minfree is reached, the p_fsize value is
2N/A * exceeded or the current file system fills up, and "audit -s" with
2N/A * changed parameters. For "audit -n" a new log file is opened
2N/A * immediately in auditd_plugin_open().
2N/A *
2N/A * This function manages one or more audit directories as follows:
2N/A *
2N/A * If the current open file is in a directory that has not
2N/A * reached the soft limit, write the input data and return.
2N/A *
2N/A * Scan the list of directories for one which has not reached
2N/A * the soft limit; if one is found, write and return. Such
2N/A * a writable directory is in "PLENTY_SPACE" state.
2N/A *
2N/A * Scan the list of directories for one which has not reached
2N/A * the hard limit; if one is found, write and return. This
2N/A * directory in in "SOFT_SPACE" state.
2N/A *
2N/A * Oh, and if a write fails, handle it like a hard space limit.
2N/A *
2N/A * audit_warn (via __audit_dowarn()) is used to alert an operator
2N/A * at various levels of fullness.
2N/A */
2N/A/* ARGSUSED */
2N/Aauditd_rc_t
2N/Aauditd_plugin(const char *input, size_t in_len, uint64_t sequence,
2N/A void *plg_ctrl, char **error)
2N/A{
2N/A auditd_rc_t rc = AUDITD_FAIL;
2N/A int open_status;
2N/A size_t out_len;
2N/A struct timeval now;
2N/A plg_ctrl_t *ctrl = plg_ctrl;
2N/A
2N/A#if DEBUG
2N/A int statrc;
2N/A#endif
2N/A
2N/A if (ctrl == NULL) {
2N/A *error = strdup(gettext("uninitialized plugin"));
2N/A return (AUDITD_RETRY);
2N/A }
2N/A
2N/A#if DEBUG
2N/A if ((ctrl->last_sequence > 0) && (sequence != ctrl->last_sequence + 1))
2N/A (void) fprintf(ctrl->dbfp,
2N/A "binfile: buffer sequence=%llu but prev=%llu=n",
2N/A sequence, ctrl->last_sequence);
2N/A ctrl->last_sequence = sequence;
2N/A
2N/A (void) fprintf(ctrl->dbfp, "binfile: input seq=%llu, len=%d\n",
2N/A sequence, in_len);
2N/A#endif
2N/A *error = NULL;
2N/A /*
2N/A * lock is for activeDir, referenced by open_log() and close_log()
2N/A */
2N/A (void) pthread_mutex_lock(&(ctrl->log_mutex));
2N/A
2N/A /*
2N/A * If this would take us over the maximum size, open a new
2N/A * file, unless maxsize is 0, in which case growth of the
2N/A * audit log is unrestricted.
2N/A */
2N/A if ((ctrl->binfile_maxsize != 0) &&
2N/A ((ctrl->binfile_cursize + in_len) > ctrl->binfile_maxsize)) {
2N/A DPRINT((ctrl->dbfp,
2N/A "binfile: maxsize exceeded, opening new audit file.\n"));
2N/A ctrl->openNewFile = B_TRUE;
2N/A }
2N/A
2N/A while (rc == AUDITD_FAIL) {
2N/A open_status = 1;
2N/A if (ctrl->openNewFile) {
2N/A open_status = open_log(ctrl->activeDir, ctrl);
2N/A if (open_status == 1) /* ok */
2N/A ctrl->openNewFile = B_FALSE;
2N/A }
2N/A /*
2N/A * consider "space ok" return and error return the same;
2N/A * a -1 means spacecheck couldn't check for space.
2N/A */
2N/A#if !DEBUG
2N/A if ((open_status == 1) &&
2N/A (spacecheck(ctrl->activeDir, ctrl->fullness_state, in_len,
2N/A ctrl) != 0)) {
2N/A#else
2N/A if ((open_status == 1) &&
2N/A (statrc = spacecheck(ctrl->activeDir, ctrl->fullness_state,
2N/A in_len, ctrl) != 0)) {
2N/A DPRINT((ctrl->dbfp,
2N/A "binfile: returned from spacecheck\n"));
2N/A /*
2N/A * The last copy of last_file_written_to is
2N/A * never free'd, so there will be one open
2N/A * memory reference on exit. It's debug only.
2N/A */
2N/A if ((ctrl->last_file_written_to != NULL) &&
2N/A (strcmp(ctrl->last_file_written_to,
2N/A ctrl->activeDir->dl_filename) != 0)) {
2N/A DPRINT((ctrl->dbfp,
2N/A "binfile: now writing to %s\n",
2N/A ctrl->activeDir->dl_filename));
2N/A free(ctrl->last_file_written_to);
2N/A }
2N/A DPRINT((ctrl->dbfp,
2N/A "binfile: finished some debug stuff\n"));
2N/A ctrl->last_file_written_to =
2N/A strdup(ctrl->activeDir->dl_filename);
2N/A#endif
2N/A out_len = write(ctrl->activeDir->dl_fd, input, in_len);
2N/A DPRINT((ctrl->dbfp, "binfile: finished the write\n"));
2N/A
2N/A ctrl->binfile_cursize += out_len;
2N/A
2N/A if (out_len == in_len) {
2N/A DPRINT((ctrl->dbfp,
2N/A "binfile: write_count=%llu, sequence=%llu,"
2N/A " l=%u\n",
2N/A ++ctrl->write_count, sequence, out_len));
2N/A ctrl->allsoftfull_warning = 0;
2N/A ctrl->activeDir->dl_flags = 0;
2N/A
2N/A rc = AUDITD_SUCCESS;
2N/A break;
2N/A } else if (!(ctrl->activeDir->dl_flags & HARD_WARNED)) {
2N/A DPRINT((ctrl->dbfp,
2N/A "binfile: write failed, sequence=%llu, "
2N/A "l=%u\n", sequence, out_len));
2N/A DPRINT((ctrl->dbfp, "hard warning sent.\n"));
2N/A __audit_dowarn("hard",
2N/A ctrl->activeDir->dl_dirname, 0);
2N/A
2N/A ctrl->activeDir->dl_flags |= HARD_WARNED;
2N/A }
2N/A } else {
2N/A DPRINT((ctrl->dbfp,
2N/A "binfile: statrc=%d, fullness_state=%d\n",
2N/A statrc, ctrl->fullness_state));
2N/A if (!(ctrl->activeDir->dl_flags & SOFT_WARNED) &&
2N/A (ctrl->activeDir->dl_space == SOFT_SPACE)) {
2N/A DPRINT((ctrl->dbfp, "soft warning sent\n"));
2N/A __audit_dowarn("soft",
2N/A ctrl->activeDir->dl_dirname, 0);
2N/A ctrl->activeDir->dl_flags |= SOFT_WARNED;
2N/A }
2N/A if (!(ctrl->activeDir->dl_flags & HARD_WARNED) &&
2N/A (ctrl->activeDir->dl_space == SPACE_FULL)) {
2N/A DPRINT((ctrl->dbfp, "hard warning sent.\n"));
2N/A __audit_dowarn("hard",
2N/A ctrl->activeDir->dl_dirname, 0);
2N/A ctrl->activeDir->dl_flags |= HARD_WARNED;
2N/A }
2N/A }
2N/A DPRINT((ctrl->dbfp, "binfile: activeDir=%s, next=%s\n",
2N/A ctrl->activeDir->dl_dirname,
2N/A ctrl->activeDir->dl_next->dl_dirname));
2N/A
2N/A ctrl->activeDir = ctrl->activeDir->dl_next;
2N/A ctrl->openNewFile = B_TRUE;
2N/A
2N/A if (ctrl->activeDir == ctrl->startdir) { /* full circle */
2N/A if (ctrl->fullness_state == PLENTY_SPACE) { /* once */
2N/A ctrl->fullness_state = SOFT_SPACE;
2N/A if (ctrl->allsoftfull_warning == 0) {
2N/A ctrl->allsoftfull_warning++;
2N/A __audit_dowarn("allsoft", "", 0);
2N/A }
2N/A } else { /* full circle twice */
2N/A if ((ctrl->hung_count > 0) &&
2N/A !(ctrl->allhard_pause)) {
2N/A ctrl->allhard_pause = 1;
2N/A (void) gettimeofday(
2N/A &(ctrl->next_allhard), NULL);
2N/A ctrl->next_allhard.tv_sec
2N/A += ALLHARD_DELAY;
2N/A }
2N/A
2N/A if (ctrl->allhard_pause) {
2N/A (void) gettimeofday(&now, NULL);
2N/A if (now.tv_sec >=
2N/A ctrl->next_allhard.tv_sec) {
2N/A ctrl->allhard_pause = 0;
2N/A __audit_dowarn("allhard", "",
2N/A ++ctrl->hung_count);
2N/A }
2N/A } else {
2N/A __audit_dowarn("allhard", "",
2N/A ++ctrl->hung_count);
2N/A }
2N/A ctrl->minfreeblocks = AVAIL_MIN;
2N/A rc = AUDITD_RETRY;
2N/A *error = strdup(gettext(
2N/A "all partitions full\n"));
2N/A }
2N/A }
2N/A }
2N/A (void) pthread_mutex_unlock(&(ctrl->log_mutex));
2N/A
2N/A return (rc);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * It may be called multiple times as auditd handles SIGHUP and SIGUSR1
2N/A * corresponding to the audit(1M) flags -s and -n
2N/A *
2N/A * kvlist is NULL only if auditd caught a SIGUSR1 (audit -n), so after the first
2N/A * time open is called; the reason is -s if kvlist != NULL and -n otherwise.
2N/A *
2N/A */
2N/Aauditd_rc_t
2N/Aauditd_plugin_open(const kva_t *kvlist, char **ret_list, void **plg_ctrl,
2N/A char **error)
2N/A{
2N/A int status;
2N/A int reason;
2N/A char *dirlist;
2N/A char *minfree;
2N/A char *maxsize;
2N/A char *host;
2N/A kva_t *kv;
2N/A plg_ctrl_t *ctrl;
2N/A
2N/A *error = NULL;
2N/A *ret_list = NULL;
2N/A kv = (kva_t *)kvlist;
2N/A
2N/A if (*plg_ctrl == NULL && !init_ctrls((plg_ctrl_t **)plg_ctrl)) {
2N/A *error = strdup(gettext("no memory"));
2N/A return (AUDITD_NO_MEMORY);
2N/A }
2N/A ctrl = *plg_ctrl;
2N/A
2N/A if (ctrl->am_open) {
2N/A if (kvlist == NULL)
2N/A reason = 1; /* audit -n */
2N/A else
2N/A reason = 2; /* audit -s */
2N/A } else {
2N/A reason = 0; /* initial open */
2N/A#if DEBUG
2N/A ctrl->dbfp = __auditd_debug_file_open();
2N/A#endif
2N/A }
2N/A DPRINT((ctrl->dbfp,
2N/A "binfile: am_open=%d, reason=%d\n", ctrl->am_open, reason));
2N/A
2N/A ctrl->am_open = B_TRUE;
2N/A
2N/A if (kvlist == NULL) {
2N/A dirlist = NULL;
2N/A minfree = NULL;
2N/A maxsize = NULL;
2N/A } else {
2N/A dirlist = kva_match(kv, "p_dir");
2N/A minfree = kva_match(kv, "p_minfree");
2N/A maxsize = kva_match(kv, "p_fsize");
2N/A }
2N/A switch (reason) {
2N/A case 0: /* initial open */
2N/A if (!ctrl->binfile_is_open) {
2N/A (void) pthread_mutex_init(&(ctrl->log_mutex), NULL);
2N/A }
2N/A ctrl->binfile_is_open = B_TRUE;
2N/A ctrl->openNewFile = B_TRUE;
2N/A if ((host = kva_match(kv, "p_host")) != NULL && *host != '\0') {
2N/A (void) snprintf(ctrl->host, sizeof (ctrl->host),
2N/A "%s", host);
2N/A }
2N/A
2N/A /* FALLTHRU */
2N/A case 2: /* audit -s */
2N/A /* handle p_fsize parameter */
2N/A save_maxsize(maxsize, ctrl);
2N/A
2N/A ctrl->fullness_state = PLENTY_SPACE;
2N/A status = loadauditlist(dirlist, minfree, ctrl);
2N/A
2N/A if (status == -1) {
2N/A *error = strdup(gettext("no directories configured"));
2N/A return (AUDITD_RETRY);
2N/A } else if (status == AUDITD_NO_MEMORY) {
2N/A *error = strdup(gettext("no memory"));
2N/A return (status);
2N/A } else { /* status is 0 or -2 (no change or changed) */
2N/A ctrl->hung_count = 0;
2N/A DPRINT((ctrl->dbfp,
2N/A "binfile: loadauditlist returned %d\n", status));
2N/A }
2N/A break;
2N/A case 1: /* audit -n */
2N/A (void) pthread_mutex_lock(&(ctrl->log_mutex));
2N/A if (open_log(ctrl->activeDir, ctrl) == 1) { /* ok */
2N/A ctrl->openNewFile = 0;
2N/A }
2N/A (void) pthread_mutex_unlock(&(ctrl->log_mutex));
2N/A break;
2N/A }
2N/A
2N/A return (AUDITD_SUCCESS);
2N/A}
2N/A
2N/Aauditd_rc_t
2N/Aauditd_plugin_close(void **plg_ctrl, char **error)
2N/A{
2N/A plg_ctrl_t *ctrl = *plg_ctrl;
2N/A *error = NULL;
2N/A
2N/A if (ctrl == NULL) {
2N/A#if DEBUG
2N/A {
2N/A FILE *dbfp;
2N/A
2N/A dbfp = __auditd_debug_file_open();
2N/A (void) fprintf(dbfp, "auditd_plugin_close() called "
2N/A "when already closed.");
2N/A (void) fflush(dbfp);
2N/A }
2N/A#endif
2N/A return (AUDITD_SUCCESS);
2N/A }
2N/A
2N/A (void) pthread_mutex_lock(&(ctrl->log_mutex));
2N/A close_log(&(ctrl->lastOpenDir), "", "", ctrl);
2N/A freedirlist(ctrl->activeDir);
2N/A (void) pthread_mutex_unlock(&(ctrl->log_mutex));
2N/A (void) pthread_mutex_destroy(&(ctrl->log_mutex));
2N/A
2N/A DPRINT((dbfp, "binfile: closed\n"));
2N/A
2N/A free(ctrl);
2N/A *plg_ctrl = NULL;
2N/A
2N/A return (AUDITD_SUCCESS);
2N/A}