/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*
* write binary audit records directly to a file.
*/
#define DEBUG 0
#if DEBUG
#else
#define DPRINT(x)
#endif
/*
* auditd_plugin_open(), auditd_plugin() and auditd_plugin_close()
* implement a replacable library for use by auditd; they are a
* project private interface and may change without notice.
*
*/
#include <assert.h>
#include <bsm/audit_record.h>
#include <errno.h>
#include <fcntl.h>
#include <libintl.h>
#include <netdb.h>
#include <pthread.h>
#include <secdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <tzfile.h>
#include <unistd.h>
#include <limits.h>
#include <syslog.h>
#include <audit_plugin.h>
/* per-directory status */
/* of blocks avail, the filesystem is */
/* presumed full. */
/* minimum reasonable size in bytes to roll over an audit file */
/*
* The directory list is a circular linked list. It is pointed into by
* activeDir. Each element contains the pointer to the next
* element, the directory pathname, a flag for how much space there is
* in the directory's filesystem, and a file handle. Since a new
* directory list can be created from auditd_plugin_open() while the
* current list is in use, activeDir is protected by log_mutex.
*/
struct dirlist_s {
int dl_space;
int dl_flags;
char *dl_dirname;
};
/*
* Defines for dl_flags
*/
#if DEBUG
#endif
static int binfile_is_open = 0;
/* flag from audit_plugin_open to audit_plugin_close */
static int am_open = 0;
/* preferred dir state */
/*
* These are used to implement a maximum size for the auditing
* file. binfile_maxsize is set via the 'p_fsize' parameter to the
* audit_binfile plugin.
*/
static void
{
/*
* Free up the old directory list if any
*/
do {
}
}
{
return (NULL);
}
return (NULL);
}
return (node_new);
}
/*
* add to a linked list of directories available for writing
*
*/
static int
{
char *dirname;
char *remainder;
else
(*count)++;
return (AUDITD_NO_MEMORY);
return (AUDITD_NO_MEMORY);
bs++;
break;
be--;
}
else
}
return (0);
}
/*
* create a linked list of directories available for writing
*
* if a list already exists, the two are compared and the new one is
* used only if it is different than the old.
*
* returns -2 for new or changed list, 0 for unchanged list and -1 for
* error. (Positive returns are for AUDITD_<error code> values)
*
*/
static int
{
int node_count = 0;
int rc;
int temp_minfree;
"(audit_binfile)\n"));
return (-1);
}
return (rc);
}
if (node_count == 0) {
/*
* there was a problem getting the directory
* list or remote host info from the audit_binfile
* configuration even though auditd thought there was
* at least 1 good entry
*/
"problem getting directory / libpath list "
"from audit_binfile configuration.\n"));
return (-1);
}
#if DEBUG
/* print out directory list */
}
}
#endif /* DEBUG */
/* See if the list has changed (rc = 0 if no change, else 1) */
rc = 0;
if (node_count == activeCount) {
n2 = activeList;
do {
"binfile: new dirname = %s\n"
"binfile: old dirname = %s\n",
n1->dl_dirname,
n2->dl_dirname));
rc = -2;
break;
}
} else {
"binfile: old dir count = %d\n"
"binfile: new dir count = %d\n",
rc = -2;
}
if (rc == -2) {
(void) pthread_mutex_lock(&log_mutex);
} else {
openNewFile = 0;
}
(void) pthread_mutex_unlock(&log_mutex);
} else {
}
/* Get the minfree value. */
if (minfreestr != NULL)
temp_minfree = 0;
if (minfree != temp_minfree) {
minfree, temp_minfree));
}
return (rc);
}
/*
* getauditdate - get the current time (GMT) and put it in the form
* yyyymmddHHMMSS .
*/
static void
{
/*
* NOTE: if we want to use gmtime, we have to be aware that the
* structure only keeps the year as an offset from TM_YEAR_BASE.
* I have used TM_YEAR_BASE in this code so that if they change
* this base from 1900 to 2000, it will hopefully mean that this
* code does not have to change. TM_YEAR_BASE is defined in
* tzfile.h .
*/
}
/*
* write_file_token - put the file token into the audit log
*/
static int
{
char token_id;
short i;
#ifdef _LP64
#else
#endif
return (errno);
}
return (0);
}
/*
* close_log - close the file if open. Also put the name of the
* new log file in the trailer, and rename the old file
* to oldname. The caller must hold log_mutext while calling
* close_log since any change to activeDir is a complete redo
* of all it points to.
* arguments -
* oldname - the new name for the file to be closed
* newname - the name of the new log file (for the trailer)
*/
static void
{
char *name;
return;
/*
* If oldname is blank, we were called by auditd_plugin_close()
* instead of by open_log, so we need to update our name.
*/
}
/*
* Write the trailer record and rename and close the file.
* If any of the write, rename, or close fail, ignore it
* since there is not much else we can do and the next open()
* will trigger the necessary full directory logic.
*
* newname is "" if binfile is being closed down.
*/
if (currentdir->dl_fd >= 0) {
}
*lastOpenDir_ptr = NULL;
}
/*
* open_log - open a new file in the current directory. If a
* file is already open, close it.
*
* return 1 if ok, 0 if all directories are full.
*
* lastOpenDir - used to get the oldfile name (and change it),
* to close the oldfile.
*
* The caller must hold log_mutex while calling open_log.
*
*/
static int
{
int opened = 0;
int error = 0;
int newfd = 0;
/* previous directory with open log file */
if (host[0] == '\0')
/* Get a filename which does not already exist */
while (!opened) {
"%s/%s.not_terminated.%s",
if (newfd < 0) {
switch (errno) {
case EEXIST:
"open_log says duplicate for %s "
"(will try another)\n", newname));
(void) sleep(1);
break;
case ENOENT: {
/* invalid path */
char *msg;
gettext("No such p_dir: %s\n"),
"open_log says about %s: %s\n",
return (0);
}
default:
/* open failed */
"open_log says full for %s: %s\n",
return (0);
} /* switch */
} else
opened = 1;
} /* while */
/*
* When we get here, we have opened our new log file.
* Now we need to update the name of the old file to
* store in this file's header. lastOpenDir may point
* to current_dir if the list is only one entry long and
* there is only one list.
*/
}
if (error) {
/* write token failed */
return (0);
} else {
}
if (lastOpenDir == NULL) {
return (0);
}
"(%s, %s [fd: %d])\n",
}
/*
* New file opened, so reset file size statistic (used
* to ensure audit log does not grow above size limit
* set by p_fsize).
*/
binfile_cursize = 0;
return (1);
}
}
/*
* spacecheck - determine whether the given directory's filesystem
* has the at least the space requested. Also set the space
* value in the directory list structure. If the caller
* passes other than PLENTY_SPACE or SOFT_SPACE, the caller should
* ignore the return value. Otherwise, 0 = less than the
* requested space is available, 1 = at least the requested space
* is available.
*
* log_mutex must be held by the caller
*
* -1 is returned if stat fails
*
* IGNORE_SIZE is one page (Sol 9 / 10 timeframe) and is the default
* buffer size written for Sol 9 and earlier. To keep the same accuracy
* for the soft limit check as before, spacecheck checks for space
* remaining IGNORE_SIZE bytes. This reduces the number of statvfs()
* calls and related math.
*
* globals -
* minfree - the soft limit, i.e., the % of filesystem to reserve
*/
static int
{
static int ignore_size = 0;
return (1);
return (-1);
} else {
ignore_size = 0;
} else
}
return (1);
}
/*
* Parses p_fsize value and contains it within the range FSIZE_MIN and
* INT_MAX so using uints won't cause an undetected overflow of
* INT_MAX. Defaults to 0 if the value is invalid or is missing.
*/
static void
/*
* strtol() returns a long which could be larger than int so
* store here for sanity checking first
*/
long proposed_maxsize;
/*
* There is no explicit error return from strtol() so
* we may need to depend on the value of errno.
*/
errno = 0;
/*
* If sizeof(long) is greater than sizeof(int) on this
* platform, proposed_maxsize might be greater than
* INT_MAX without it being reported as ERANGE.
*/
((proposed_maxsize != 0) &&
(proposed_maxsize < FSIZE_MIN)) ||
(proposed_maxsize > INT_MAX)) {
binfile_maxsize = 0;
"range: %s\n", maxsize));
/*
* Inform administrator of the error via
* syslog
*/
gettext("p_fsize parameter out of range\n"));
} else {
}
} else { /* p_fsize string was not present */
binfile_maxsize = 0;
}
}
/*
* auditd_plugin() writes a buffer to the currently open file. The
* global "openNewFile" is used to force a new log file for cases such
* as the initial open, when minfree is reached, the p_fsize value is
* exceeded or the current file system fills up, and "audit -s" with
* changed parameters. For "audit -n" a new log file is opened
* immediately in auditd_plugin_open().
*
* This function manages one or more audit directories as follows:
*
* If the current open file is in a directory that has not
* reached the soft limit, write the input data and return.
*
* Scan the list of directories for one which has not reached
* the soft limit; if one is found, write and return. Such
* a writable directory is in "PLENTY_SPACE" state.
*
* Scan the list of directories for one which has not reached
* the hard limit; if one is found, write and return. This
* directory in in "SOFT_SPACE" state.
*
* Oh, and if a write fails, handle it like a hard space limit.
*
* audit_warn (via __audit_dowarn()) is used to alert an operator
* at various levels of fullness.
*/
/* ARGSUSED */
{
int open_status;
/* avoid excess audit_warnage */
static int allsoftfull_warning = 0;
static int allhard_pause = 0;
#if DEBUG
int statrc;
"binfile: buffer sequence=%llu but prev=%llu=n",
#endif
/*
* lock is for activeDir, referenced by open_log() and close_log()
*/
(void) pthread_mutex_lock(&log_mutex);
/*
* If this would take us over the maximum size, open a new
* file, unless maxsize is 0, in which case growth of the
* audit log is unrestricted.
*/
if ((binfile_maxsize != 0) &&
"file.\n"));
openNewFile = 1;
}
while (rc == AUDITD_FAIL) {
open_status = 1;
if (openNewFile) {
openNewFile = 0;
}
/*
* consider "space ok" return and error return the same;
* a -1 means spacecheck couldn't check for space.
*/
#if !DEBUG
if ((open_status == 1) &&
#else
if ((open_status == 1) &&
in_len) != 0)) {
/*
* The last copy of last_file_written_to is
* never free'd, so there will be one open
* memory reference on exit. It's debug only.
*/
if ((last_file_written_to != NULL) &&
activeDir->dl_filename) != 0)) {
activeDir->dl_filename));
}
#endif
"binfile: write_count=%llu, sequence=%llu,"
" l=%u\n",
allsoftfull_warning = 0;
rc = AUDITD_SUCCESS;
break;
"binfile: write failed, sequence=%llu, "
0);
}
} else {
statrc, fullness_state));
__audit_dowarn("soft",
activeDir->dl_dirname, 0);
}
__audit_dowarn("hard",
activeDir->dl_dirname, 0);
}
}
openNewFile = 1;
if (allsoftfull_warning == 0) {
}
} else { /* full circle twice */
if ((hung_count > 0) && !allhard_pause) {
allhard_pause = 1;
(void) gettimeofday(&next_allhard,
NULL);
}
if (allhard_pause) {
allhard_pause = 0;
++hung_count);
}
} else {
++hung_count);
}
rc = AUDITD_RETRY;
"all partitions full\n"));
(void) __logpost("");
}
}
}
(void) pthread_mutex_unlock(&log_mutex);
return (rc);
}
/*
* It may be called multiple times as auditd handles SIGHUP and SIGUSR1
* corresponding to the audit(1M) flags -s and -n
*
* kvlist is NULL only if auditd caught a SIGUSR1 (audit -n), so after the first
* time open is called; the reason is -s if kvlist != NULL and -n otherwise.
*
*/
{
int rc = 0;
int status;
int reason;
char *dirlist;
char *minfree;
char *maxsize;
if (am_open) {
else
} else {
reason = 0; /* initial open */
#if DEBUG
#endif
}
am_open = 1;
} else {
}
switch (reason) {
case 0: /* initial open */
if (!binfile_is_open)
binfile_is_open = 1;
openNewFile = 1;
/* FALLTHRU */
case 2: /* audit -s */
/* handle p_fsize parameter */
if (status == -1) {
(void) __logpost("");
return (AUDITD_RETRY);
} else if (status == AUDITD_NO_MEMORY) {
(void) __logpost("");
return (status);
} else { /* status is 0 or -2 (no change or changed) */
hung_count = 0;
status));
}
break;
case 1: /* audit -n */
(void) pthread_mutex_lock(&log_mutex);
openNewFile = 0;
(void) pthread_mutex_unlock(&log_mutex);
break;
}
rc = AUDITD_SUCCESS;
return (rc);
}
{
(void) pthread_mutex_lock(&log_mutex);
(void) pthread_mutex_unlock(&log_mutex);
(void) __logpost("");
if (binfile_is_open) {
(void) pthread_mutex_destroy(&log_mutex);
binfile_is_open = 0;
#if DEBUG
} else {
"auditd_plugin_close() called when already closed.");
#endif
}
am_open = 0;
return (AUDITD_SUCCESS);
}