/*- * 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 #include #include #ifdef HAVE_FULL_QUEUE_H #include #else /* !HAVE_FULL_QUEUE_H */ #include #endif /* !HAVE_FULL_QUEUE_H */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "auditd.h" #ifndef HAVE_STRLCPY #include #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); }