freebsd-dev/bin/auditd/auditd.c
Robert Watson a4bd134433 Vendor import of OpenBSM 1.1 alpha5, which incorporates the following
changes since the last imported OpenBSM release:

OpenBSM 1.1 alpha 5

- Stub libauditd(3) man page added.
- All BSM error number constants with BSM_ERRNO_.
- Interfaces to convert between local and BSM socket types and protocol
  families have been added: au_bsm_to_domain(3), au_bsm_to_socket_type(3),
  au_domain_to_bsm(3), and au_socket_type_to_bsm(3), along with definitions
  of constants in audit_domain.h and audit_socket_type.h.  This improves
  interoperability by converting local constant spaces, which vary by OS, to
  and from Solaris constants (where available) or OpenBSM constants for
  protocol domains not present in Solaris (a fair number).  These routines
  should be used when generating and interpreting extended socket tokens.
- Fix build warnings with full gcc warnings enabled on most supported
  platforms.
- Don't compile error strings into bsm_errno.c when building it in the kernel
  environment.
- When started by launchd, use the label com.apple.auditd rather than
  org.trustedbsd.auditd.

Obtained from:    TrustedBSD Project
Sponsored by:     Apple Inc.
2009-01-11 21:24:07 +00:00

798 lines
18 KiB
C

/*-
* Copyright (c) 2004-2008 Apple Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $P4: //depot/projects/trustedbsd/openbsm/bin/auditd/auditd.c#41 $
*/
#include <sys/types.h>
#include <config/config.h>
#include <sys/dirent.h>
#ifdef HAVE_FULL_QUEUE_H
#include <sys/queue.h>
#else /* !HAVE_FULL_QUEUE_H */
#include <compat/queue.h>
#endif /* !HAVE_FULL_QUEUE_H */
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <bsm/audit.h>
#include <bsm/audit_uevents.h>
#include <bsm/auditd_lib.h>
#include <bsm/libbsm.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include "auditd.h"
#ifndef HAVE_STRLCPY
#include <compat/strlcpy.h>
#endif
/*
* XXX the following is temporary until this can be added to the kernel
* audit.h header.
*/
#ifndef AUDIT_TRIGGER_INITIALIZE
#define AUDIT_TRIGGER_INITIALIZE 7
#endif
/*
* LaunchD flag (Mac OS X and, maybe, FreeBSD only.) See launchd(8) and
* http://wiki.freebsd.org/launchd for more information.
*
* In order for auditd to work "on demand" with launchd(8) it can't:
* call daemon(3)
* call fork and having the parent process exit
* change uids or gids.
* set up the current working directory or chroot.
* set the session id
* change stdio to /dev/null.
* call setrusage(2)
* call setpriority(2)
* Ignore SIGTERM.
* auditd (in 'launchd mode') is launched on demand so it must catch
* SIGTERM to exit cleanly.
*/
static int launchd_flag = 0;
/*
* The GID of the audit review group (if used). The audit trail files and
* system logs (Mac OS X only) can only be reviewed by members of this group
* or the audit administrator (aka. "root").
*/
static gid_t audit_review_gid = -1;
/*
* The path and file name of the last audit trail file.
*/
static char *lastfile = NULL;
/*
* Error starting auditd. Run warn script and exit.
*/
static void
fail_exit(void)
{
audit_warn_nostart();
exit(1);
}
/*
* Follow the 'current' symlink to get the active trail file name.
*/
static char *
get_curfile(void)
{
char *cf;
int len;
cf = malloc(MAXPATHLEN);
if (cf == NULL) {
auditd_log_err("malloc failed: %m");
return (NULL);
}
len = readlink(AUDIT_CURRENT_LINK, cf, MAXPATHLEN - 1);
if (len < 0) {
free(cf);
return (NULL);
}
/* readlink() doesn't terminate string. */
cf[len] = '\0';
return (cf);
}
/*
* Close the previous audit trail file.
*/
static int
close_lastfile(char *TS)
{
char *ptr;
char *oldname;
size_t len;
/* If lastfile is NULL try to get it from the 'current' link. */
if (lastfile == NULL)
lastfile = get_curfile();
if (lastfile != NULL) {
len = strlen(lastfile) + 1;
oldname = (char *)malloc(len);
if (oldname == NULL)
return (-1);
strlcpy(oldname, lastfile, len);
/* Rename the last file -- append timestamp. */
if ((ptr = strstr(lastfile, NOT_TERMINATED)) != NULL) {
strlcpy(ptr, TS, TIMESTAMP_LEN);
if (rename(oldname, lastfile) != 0)
auditd_log_err(
"Could not rename %s to %s: %m", oldname,
lastfile);
else {
/*
* Remove the 'current' symlink since the link
* is now invalid.
*/
(void) unlink(AUDIT_CURRENT_LINK);
auditd_log_notice( "renamed %s to %s",
oldname, lastfile);
audit_warn_closefile(lastfile);
}
} else
auditd_log_err( "Could not rename %s to %s", oldname,
lastfile);
free(lastfile);
free(oldname);
lastfile = NULL;
}
return (0);
}
/*
* Create the new file name, swap with existing audit file.
*/
static int
swap_audit_file(void)
{
int err;
char *newfile;
char TS[TIMESTAMP_LEN];
time_t tt;
if (getTSstr(tt, TS, TIMESTAMP_LEN) != 0)
return (-1);
err = auditd_swap_trail(TS, &newfile, audit_review_gid,
audit_warn_getacdir);
if (err != ADE_NOERR) {
auditd_log_err( "%s: %m", auditd_strerror(err));
if (err != ADE_ACTL)
return (-1);
}
/*
* Only close the last file if were in an auditing state before
* calling swap_audit_file(). We may need to recover from a crash.
*/
if (auditd_get_state() == AUD_STATE_ENABLED)
close_lastfile(TS);
/*
* auditd_swap_trail() potentially enables auditing (if not already
* enabled) so updated the cached state as well.
*/
auditd_set_state(AUD_STATE_ENABLED);
/*
* Create 'current' symlink. Recover from crash, if needed.
*/
if (auditd_new_curlink(newfile) != 0)
auditd_log_err("auditd_new_curlink(\"%s\") failed: %s: %m",
newfile, auditd_strerror(err));
lastfile = newfile;
auditd_log_notice("New audit file is %s", newfile);
return (0);
}
/*
* Create a new audit log trail file and swap with the current one, if any.
*/
static int
do_trail_file(void)
{
int err;
/*
* First, refresh the list of audit log directories.
*/
err = auditd_read_dirs(audit_warn_soft, audit_warn_hard);
if (err) {
auditd_log_err("auditd_read_dirs(): %s",
auditd_strerror(err));
if (err == ADE_HARDLIM)
audit_warn_allhard();
if (err != ADE_SOFTLIM)
return (-1);
else
audit_warn_allsoft();
/* continue on with soft limit error */
}
/*
* Create a new file and swap with the one being used in kernel.
*/
if (swap_audit_file() == -1) {
/*
* XXX Faulty directory listing? - user should be given
* XXX an opportunity to change the audit_control file
* XXX switch to a reduced mode of auditing?
*/
return (-1);
}
return (0);
}
/*
* Start up auditing.
*/
static void
audit_setup(void)
{
int err;
if (do_trail_file() == -1) {
auditd_log_err("Error creating audit trail file");
fail_exit();
}
/* Generate an audit record. */
err = auditd_gen_record(AUE_audit_startup, NULL);
if (err)
auditd_log_err("auditd_gen_record(AUE_audit_startup) %s: %m",
auditd_strerror(err));
if (auditd_config_controls() == 0)
auditd_log_info("Audit controls init successful");
else
auditd_log_err("Audit controls init failed");
}
/*
* Close auditd pid file and trigger mechanism.
*/
static int
close_misc(void)
{
auditd_close_dirs();
if (unlink(AUDITD_PIDFILE) == -1 && errno != ENOENT) {
auditd_log_err("Couldn't remove %s: %m", AUDITD_PIDFILE);
return (1);
}
endac();
if (auditd_close_trigger() != 0) {
auditd_log_err("Error closing trigger messaging mechanism");
return (1);
}
return (0);
}
/*
* Close all log files, control files, and tell the audit system.
*/
static int
close_all(void)
{
int err_ret = 0;
char TS[TIMESTAMP_LEN];
int err;
long cond;
time_t tt;
err = auditd_gen_record(AUE_audit_shutdown, NULL);
if (err)
auditd_log_err("auditd_gen_record(AUE_audit_shutdown) %s: %m",
auditd_strerror(err));
/* Flush contents. */
cond = AUC_DISABLED;
err_ret = auditon(A_SETCOND, &cond, sizeof(cond));
if (err_ret != 0) {
auditd_log_err("Disabling audit failed! : %s", strerror(errno));
err_ret = 1;
}
/*
* Updated the cached state that auditing has been disabled.
*/
auditd_set_state(AUD_STATE_DISABLED);
if (getTSstr(tt, TS, TIMESTAMP_LEN) == 0)
close_lastfile(TS);
if (lastfile != NULL)
free(lastfile);
err_ret += close_misc();
if (err_ret) {
auditd_log_err("Could not unregister");
audit_warn_postsigterm();
}
auditd_log_info("Finished");
return (err_ret);
}
/*
* Register the daemon with the signal handler and the auditd pid file.
*/
static int
register_daemon(void)
{
FILE * pidfile;
int fd;
pid_t pid;
/* Set up the signal hander. */
if (signal(SIGTERM, auditd_relay_signal) == SIG_ERR) {
auditd_log_err(
"Could not set signal handler for SIGTERM");
fail_exit();
}
if (signal(SIGCHLD, auditd_relay_signal) == SIG_ERR) {
auditd_log_err(
"Could not set signal handler for SIGCHLD");
fail_exit();
}
if (signal(SIGHUP, auditd_relay_signal) == SIG_ERR) {
auditd_log_err(
"Could not set signal handler for SIGHUP");
fail_exit();
}
if (signal(SIGALRM, auditd_relay_signal) == SIG_ERR) {
auditd_log_err(
"Could not set signal handler for SIGALRM");
fail_exit();
}
if ((pidfile = fopen(AUDITD_PIDFILE, "a")) == NULL) {
auditd_log_err("Could not open PID file");
audit_warn_tmpfile();
return (-1);
}
/* Attempt to lock the pid file; if a lock is present, exit. */
fd = fileno(pidfile);
if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
auditd_log_err(
"PID file is locked (is another auditd running?).");
audit_warn_ebusy();
return (-1);
}
pid = getpid();
ftruncate(fd, 0);
if (fprintf(pidfile, "%u\n", pid) < 0) {
/* Should not start the daemon. */
fail_exit();
}
fflush(pidfile);
return (0);
}
/*
* Handle the audit trigger event.
*
* We suppress (ignore) duplicated triggers in close succession in order to
* try to avoid thrashing-like behavior. However, not all triggers can be
* ignored, as triggers generally represent edge triggers, not level
* triggers, and won't be retransmitted if the condition persists. Of
* specific concern is the rotate trigger -- if one is dropped, then it will
* not be retransmitted, and the log file will grow in an unbounded fashion.
*/
#define DUPLICATE_INTERVAL 30
void
auditd_handle_trigger(int trigger)
{
static int last_trigger, last_warning;
static time_t last_time;
struct timeval ts;
struct timezone tzp;
time_t tt;
int au_state;
int err = 0;
/*
* Suppress duplicate messages from the kernel within the specified
* interval.
*/
if (gettimeofday(&ts, &tzp) == 0) {
tt = (time_t)ts.tv_sec;
switch (trigger) {
case AUDIT_TRIGGER_LOW_SPACE:
case AUDIT_TRIGGER_NO_SPACE:
/*
* Triggers we can suppress. Of course, we also need
* to rate limit the warnings, so apply the same
* interval limit on syslog messages.
*/
if ((trigger == last_trigger) &&
(tt < (last_time + DUPLICATE_INTERVAL))) {
if (tt >= (last_warning + DUPLICATE_INTERVAL))
auditd_log_info(
"Suppressing duplicate trigger %d",
trigger);
return;
}
last_warning = tt;
break;
case AUDIT_TRIGGER_ROTATE_KERNEL:
case AUDIT_TRIGGER_ROTATE_USER:
case AUDIT_TRIGGER_READ_FILE:
case AUDIT_TRIGGER_CLOSE_AND_DIE:
case AUDIT_TRIGGER_INITIALIZE:
/*
* Triggers that we cannot suppress.
*/
break;
}
/*
* Only update last_trigger after aborting due to a duplicate
* trigger, not before, or we will never allow that trigger
* again.
*/
last_trigger = trigger;
last_time = tt;
}
au_state = auditd_get_state();
/*
* Message processing is done here.
*/
switch(trigger) {
case AUDIT_TRIGGER_LOW_SPACE:
auditd_log_notice("Got low space trigger");
if (do_trail_file() == -1)
auditd_log_err("Error swapping audit file");
break;
case AUDIT_TRIGGER_NO_SPACE:
auditd_log_notice("Got no space trigger");
if (do_trail_file() == -1)
auditd_log_err("Error swapping audit file");
break;
case AUDIT_TRIGGER_ROTATE_KERNEL:
case AUDIT_TRIGGER_ROTATE_USER:
auditd_log_info("Got open new trigger from %s", trigger ==
AUDIT_TRIGGER_ROTATE_KERNEL ? "kernel" : "user");
if (au_state == AUD_STATE_ENABLED && do_trail_file() == -1)
auditd_log_err("Error swapping audit file");
break;
case AUDIT_TRIGGER_READ_FILE:
auditd_log_info("Got read file trigger");
if (au_state == AUD_STATE_ENABLED &&
auditd_config_controls() == -1)
auditd_log_err("Error setting audit controls");
break;
case AUDIT_TRIGGER_CLOSE_AND_DIE:
auditd_log_info("Got close and die trigger");
if (au_state == AUD_STATE_ENABLED)
err = close_all();
/*
* Running under launchd don't exit. Wait for launchd to
* send SIGTERM.
*/
if (!launchd_flag) {
auditd_log_info("auditd exiting.");
exit (err);
}
break;
case AUDIT_TRIGGER_INITIALIZE:
auditd_log_info("Got audit initialize trigger");
if (au_state == AUD_STATE_DISABLED)
audit_setup();
break;
default:
auditd_log_err("Got unknown trigger %d", trigger);
break;
}
}
/*
* Reap our children.
*/
void
auditd_reap_children(void)
{
pid_t child;
int wstatus;
while ((child = waitpid(-1, &wstatus, WNOHANG)) > 0) {
if (!wstatus)
continue;
auditd_log_info("warn process [pid=%d] %s %d.", child,
((WIFEXITED(wstatus)) ? "exited with non-zero status" :
"exited as a result of signal"),
((WIFEXITED(wstatus)) ? WEXITSTATUS(wstatus) :
WTERMSIG(wstatus)));
}
}
/*
* Reap any children and terminate. If under launchd don't shutdown auditing
* but just the other stuff.
*/
void
auditd_terminate(void)
{
int ret;
auditd_reap_children();
if (launchd_flag)
ret = close_misc();
else
ret = close_all();
exit(ret);
}
/*
* Configure the audit controls in the kernel: the event to class mapping,
* kernel preselection mask, etc.
*/
int
auditd_config_controls(void)
{
int cnt, err;
int ret = 0;
/*
* Configure event to class mappings in kernel.
*/
cnt = auditd_set_evcmap();
if (cnt < 0) {
auditd_log_err("auditd_set_evcmap() failed: %m");
ret = -1;
} else if (cnt == 0) {
auditd_log_err("No events to class mappings registered.");
ret = -1;
} else
auditd_log_debug("Registered %d event to class mappings.", cnt);
/*
* Configure non-attributable event mask in kernel.
*/
err = auditd_set_namask();
if (err) {
auditd_log_err("auditd_set_namask() %s: %m",
auditd_strerror(err));
ret = -1;
} else
auditd_log_debug("Registered non-attributable event mask.");
/*
* Configure audit policy in kernel.
*/
err = auditd_set_policy();
if (err) {
auditd_log_err("auditd_set_policy() %s: %m",
auditd_strerror(err));
ret = -1;
} else
auditd_log_debug("Set audit policy in kernel.");
/*
* Configure audit trail log size in kernel.
*/
err = auditd_set_fsize();
if (err) {
auditd_log_err("audit_set_fsize() %s: %m",
auditd_strerror(err));
ret = -1;
} else
auditd_log_debug("Set audit trail size in kernel.");
/*
* Configure audit trail volume minimum free percentage of blocks in
* kernel.
*/
err = auditd_set_minfree();
if (err) {
auditd_log_err("auditd_set_minfree() %s: %m",
auditd_strerror(err));
ret = -1;
} else
auditd_log_debug(
"Set audit trail min free percent in kernel.");
/*
* Configure host address in the audit kernel information.
*/
err = auditd_set_host();
if (err) {
auditd_log_err("auditd_set_host() %s: %m",
auditd_strerror(err));
ret = -1;
} else
auditd_log_debug(
"Set audit host address information in kernel.");
return (ret);
}
/*
* Setup and initialize auditd.
*/
static void
setup(void)
{
int err;
if (auditd_open_trigger(launchd_flag) < 0) {
auditd_log_err("Error opening trigger messaging mechanism");
fail_exit();
}
/*
* To prevent event feedback cycles and avoid auditd becoming
* stalled if auditing is suspended, auditd and its children run
* without their events being audited. We allow the uid, tid, and
* mask fields to be implicitly set to zero, but do set the pid. We
* run this after opening the trigger device to avoid configuring
* audit state without audit present in the system.
*/
err = auditd_prevent_audit();
if (err) {
auditd_log_err("auditd_prevent_audit() %s: %m",
auditd_strerror(err));
fail_exit();
}
/*
* Make sure auditd auditing state is correct.
*/
auditd_set_state(AUD_STATE_INIT);
/*
* If under launchd, don't start auditing. Wait for a trigger to
* do so.
*/
if (!launchd_flag)
audit_setup();
}
int
main(int argc, char **argv)
{
int ch;
int debug = 0;
#ifdef AUDIT_REVIEW_GROUP
struct group *grp;
#endif
while ((ch = getopt(argc, argv, "dl")) != -1) {
switch(ch) {
case 'd':
/* Debug option. */
debug = 1;
break;
case 'l':
/* Be launchd friendly. */
launchd_flag = 1;
break;
case '?':
default:
(void)fprintf(stderr,
"usage: auditd [-d] [-l]\n");
exit(1);
}
}
audit_review_gid = getgid();
#ifdef AUDIT_REVIEW_GROUP
/*
* XXXRW: Currently, this code falls back to the daemon gid, which is
* likely the wheel group. Is there a better way to deal with this?
*/
grp = getgrnam(AUDIT_REVIEW_GROUP);
if (grp != NULL)
audit_review_gid = grp->gr_gid;
#endif
auditd_openlog(debug, audit_review_gid);
if (launchd_flag)
auditd_log_info("started by launchd...");
else
auditd_log_info("starting...");
#ifdef AUDIT_REVIEW_GROUP
if (grp == NULL)
auditd_log_info(
"Audit review group '%s' not available, using daemon gid (%d)",
AUDIT_REVIEW_GROUP, audit_review_gid);
#endif
if (debug == 0 && launchd_flag == 0 && daemon(0, 0) == -1) {
auditd_log_err("Failed to daemonize");
exit(1);
}
if (register_daemon() == -1) {
auditd_log_err("Could not register as daemon");
exit(1);
}
setup();
/*
* auditd_wait_for_events() shouldn't return unless something is wrong.
*/
auditd_wait_for_events();
auditd_log_err("abnormal exit.");
close_all();
exit(-1);
}