+/*
+ * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights
+ * Reserved. This file contains Original Code and/or Modifications of
+ * Original Code as defined in and that are subject to the Apple Public
+ * Source License Version 1.0 (the 'License'). You may not use this file
+ * except in compliance with the License. Please obtain a copy of the
+ * License at http://www.apple.com/publicsource and read it before using
+ * this file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
+ * License for the specific language governing rights and limitations
+ * under the License."
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <mach/port.h>
+#include <mach/mach_error.h>
+#include <mach/mach_traps.h>
+#include <mach/mach.h>
+#include <mach/host_special_ports.h>
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <fcntl.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <syslog.h>
+#include <signal.h>
+#include <string.h>
+#include <notify.h>
+
+#include <bsm/audit.h>
+#include <bsm/audit_uevents.h>
+#include <bsm/libbsm.h>
+
+#include <auditd.h>
+#include "auditd_control_server.h"
+#include "audit_triggers_server.h"
+#define NA_EVENT_STR_SIZE 25
+
+static int ret, minval;
+static char *lastfile = NULL;
+
+static int allhardcount = 0;
+
+mach_port_t bp = MACH_PORT_NULL;
+mach_port_t control_port = MACH_PORT_NULL;
+mach_port_t signal_port = MACH_PORT_NULL;
+mach_port_t port_set = MACH_PORT_NULL;
+
+#ifndef __BSM_INTERNAL_NOTIFY_KEY
+#define __BSM_INTERNAL_NOTIFY_KEY "com.apple.audit.change"
+#endif /* __BSM_INTERNAL_NOTIFY_KEY */
+
+TAILQ_HEAD(, dir_ent) dir_q;
+
+
+/* Error starting auditd */
+void fail_exit()
+{
+ audit_warn_nostart();
+ exit(1);
+}
+
+/*
+ * Free our local list of directory names
+ */
+void free_dir_q()
+{
+ struct dir_ent *dirent;
+
+ while ((dirent = TAILQ_FIRST(&dir_q))) {
+ TAILQ_REMOVE(&dir_q, dirent, dirs);
+ free(dirent->dirname);
+ free(dirent);
+ }
+}
+
+/*
+ * generate the timestamp string
+ */
+int getTSstr(char *buf, int len)
+{
+ struct timeval ts;
+ struct timezone tzp;
+ time_t tt;
+
+ if(gettimeofday(&ts, &tzp) != 0) {
+ return -1;
+ }
+ tt = (time_t)ts.tv_sec;
+ if(!strftime(buf, len, "%Y%m%d%H%M%S", gmtime(&tt))) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Concat the directory name to the given file name
+ * XXX We should affix the hostname also
+ */
+char *affixdir(char *name, struct dir_ent *dirent)
+{
+ char *fn;
+ char *curdir;
+ const char *sep = "/";
+
+ curdir = dirent->dirname;
+ syslog(LOG_INFO, "dir = %s\n", dirent->dirname);
+
+ fn = (char *) malloc (strlen(curdir) + strlen(sep)
+ + (2 * POSTFIX_LEN) + 1);
+ if(fn == NULL) {
+ return NULL;
+ }
+ strcpy(fn, curdir);
+ strcat(fn, sep);
+ strcat(fn, name);
+
+ return fn;
+}
+
+/* Close the previous audit trail file */
+int close_lastfile(char *TS)
+{
+ char *ptr;
+ char *oldname;
+
+ if(lastfile != NULL) {
+ oldname = (char *)malloc(strlen(lastfile) + 1);
+ if(oldname == NULL) {
+ return -1;
+ }
+ strcpy(oldname, lastfile);
+
+ /* rename the last file -- append timestamp */
+
+ if((ptr = strstr(lastfile, NOT_TERMINATED)) != NULL) {
+ *ptr = '.';
+ strcpy(ptr+1, TS);
+ if(rename(oldname, lastfile) != 0) {
+ syslog(LOG_ERR, "Could not rename %s to %s \n",
+ oldname, lastfile);
+ }
+ else {
+ syslog(LOG_INFO, "renamed %s to %s \n",
+ oldname, lastfile);
+ }
+ }
+
+ free(lastfile);
+ free(oldname);
+
+ lastfile = NULL;
+ }
+
+ return 0;
+}
+
+/*
+ * Create the new file name, swap with existing audit file
+ */
+int swap_audit_file()
+{
+ char timestr[2 * POSTFIX_LEN];
+ char *fn;
+ char TS[POSTFIX_LEN];
+ struct dir_ent *dirent;
+
+ if(getTSstr(TS, POSTFIX_LEN) != 0) {
+ return -1;
+ }
+
+ strcpy(timestr, TS);
+ strcat(timestr, NOT_TERMINATED);
+
+ /* try until we succeed */
+ while((dirent = TAILQ_FIRST(&dir_q))) {
+ if((fn = affixdir(timestr, dirent)) == NULL) {
+ return -1;
+ }
+
+ syslog(LOG_INFO, "New audit file is %s\n", fn);
+ if (open(fn, O_RDONLY | O_CREAT, S_IRUSR | S_IRGRP) < 0) {
+ perror("File open");
+ }
+ else if (auditctl(fn) != 0) {
+ syslog(LOG_ERR, "auditctl failed! : %s\n",
+ strerror(errno));
+ }
+ else {
+ /* Success */
+ close_lastfile(TS);
+ lastfile = fn;
+ return 0;
+ }
+
+ /* Tell the administrator about lack of permissions for dirent */
+ audit_warn_getacdir(dirent->dirname);
+
+ /* Try again with a different directory */
+ TAILQ_REMOVE(&dir_q, dirent, dirs);
+ free(dirent->dirname);
+ free(dirent);
+ }
+ return -1;
+}
+
+/*
+ * Read the audit_control file contents
+ */
+int read_control_file()
+{
+ char cur_dir[MAX_DIR_SIZE];
+ struct dir_ent *dirent;
+ au_qctrl_t qctrl;
+
+ /* Clear old values */
+ free_dir_q();
+ endac(); // force a re-read of the file the next time
+
+ /* Post that the audit config changed */
+ notify_post(__BSM_INTERNAL_NOTIFY_KEY);
+
+ /* Read the list of directories into a local linked list */
+ /* XXX We should use the reentrant interfaces once they are available */
+ while(getacdir(cur_dir, MAX_DIR_SIZE) >= 0) {
+ dirent = (struct dir_ent *) malloc (sizeof(struct dir_ent));
+ if(dirent == NULL) {
+ return -1;
+ }
+
+ dirent->softlim = 0;
+ dirent->dirname = (char *) malloc (MAX_DIR_SIZE);
+ if(dirent->dirname == NULL) {
+ free(dirent);
+ return -1;
+ }
+
+ strcpy(dirent->dirname, cur_dir);
+ TAILQ_INSERT_TAIL(&dir_q, dirent, dirs);
+ }
+
+ allhardcount = 0;
+
+ if(swap_audit_file() == -1) {
+ syslog(LOG_ERR, "Could not swap audit file\n");
+ /*
+ * 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;
+ }
+
+ /*
+ * XXX There are synchronization problems here
+ * XXX what should we do if a trigger for the earlier limit
+ * XXX is generated here?
+ */
+ if(0 == (ret = getacmin(&minval))) {
+
+ syslog(LOG_INFO, "min free = %d\n", minval);
+
+ if (auditon(A_GETQCTRL, &qctrl, sizeof(qctrl)) != 0) {
+ syslog(LOG_ERR,
+ "could not get audit queue settings\n");
+ return -1;
+ }
+ qctrl.aq_minfree = minval;
+ if (auditon(A_SETQCTRL, &qctrl, sizeof(qctrl)) != 0) {
+ syslog(LOG_ERR,
+ "could not set audit queue settings\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Close all log files, control files, and tell the audit system.
+ */
+int close_all()
+{
+ int err_ret = 0;
+ char TS[POSTFIX_LEN];
+ int aufd;
+ token_t *tok;
+
+ /* Generate an audit record */
+ if((aufd = au_open()) == -1) {
+ syslog(LOG_ERR, "Could not create audit shutdown event.\n");
+ } else {
+
+ if((tok = au_to_text("auditd::Audit shutdown")) != NULL) {
+ au_write(aufd, tok);
+ }
+
+ if(au_close(aufd, 1, AUE_audit_shutdown) == -1) {
+ syslog(LOG_ERR, "Could not close audit shutdown event.\n");
+ }
+ }
+
+ /* flush contents */
+ err_ret = auditctl(NULL);
+ if (err_ret != 0) {
+ syslog(LOG_ERR, "auditctl failed! : %s\n",
+ strerror(errno));
+ err_ret = 1;
+ }
+ if(getTSstr(TS, POSTFIX_LEN) == 0) {
+ close_lastfile(TS);
+ }
+ if(lastfile != NULL)
+ free(lastfile);
+
+ free_dir_q();
+ if((remove(AUDITD_PIDFILE) == -1) || err_ret) {
+ syslog(LOG_ERR, "Could not unregister\n");
+ audit_warn_postsigterm();
+ return (1);
+ }
+ endac();
+ syslog(LOG_INFO, "Finished.\n");
+ return (0);
+}
+
+/*
+ * When we get a signal, we are often not at a clean point.
+ * So, little can be done in the signal handler itself. Instead,
+ * we send a message to the main servicing loop to do proper
+ * handling from a non-signal-handler context.
+ */
+static void
+relay_signal(int signal)
+{
+ mach_msg_empty_send_t msg;
+
+ msg.header.msgh_id = signal;
+ msg.header.msgh_remote_port = signal_port;
+ msg.header.msgh_local_port = MACH_PORT_NULL;
+ msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
+ mach_msg(&(msg.header), MACH_SEND_MSG|MACH_SEND_TIMEOUT, sizeof(msg),
+ 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+}
+
+/* registering the daemon */
+int register_daemon()
+{
+ FILE * pidfile;
+ int fd;
+ pid_t pid;
+
+ /* Set up the signal hander */
+ if (signal(SIGTERM, relay_signal) == SIG_ERR) {
+ fail_exit();
+ }
+ if (signal(SIGCHLD, relay_signal) == SIG_ERR) {
+ fail_exit();
+ }
+
+ if ((pidfile = fopen(AUDITD_PIDFILE, "a")) == NULL) {
+ 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) {
+ syslog(LOG_ERR, "PID file is locked (is another auditd running?).\n");
+ 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;
+}
+
+/*
+ * React to input from the audit tool
+ */
+kern_return_t auditd_control(auditd_port, flags)
+ mach_port_t auditd_port;
+ int flags;
+{
+ int err_ret = 0;
+
+ switch(flags) {
+
+ case OPEN_NEW :
+ /* create a new file and swap with the one being used in kernel */
+ if(swap_audit_file() == -1) {
+ syslog(LOG_ERR, "Error swapping audit file\n");
+ }
+ break;
+
+ case READ_FILE :
+ if(read_control_file() == -1) {
+ syslog(LOG_ERR, "Error in audit control file\n");
+ }
+ break;
+
+ case CLOSE_AND_DIE :
+ err_ret = close_all();
+ exit (err_ret);
+ break;
+
+ default :
+ break;
+ }
+
+ return KERN_SUCCESS;
+}
+
+/*
+ * Suppress duplicate messages within a 30 second interval.
+ * This should be enough to time to rotate log files without
+ * thrashing from soft warnings generated before the log is
+ * actually rotated.
+ */
+#define DUPLICATE_INTERVAL 30
+/*
+ * Implementation of the audit_triggers() MIG routine.
+ */
+kern_return_t audit_triggers(audit_port, flags)
+ mach_port_t audit_port;
+ int flags;
+{
+ static int last_flags;
+ static time_t last_time;
+ struct dir_ent *dirent;
+
+ /*
+ * Suppres duplicate messages from the kernel within the specified interval
+ */
+ struct timeval ts;
+ struct timezone tzp;
+ time_t tt;
+
+ if(gettimeofday(&ts, &tzp) == 0) {
+ tt = (time_t)ts.tv_sec;
+ if ((flags == last_flags) && (tt < (last_time + DUPLICATE_INTERVAL))) {
+ return KERN_SUCCESS;
+ }
+ last_flags = flags;
+ last_time = tt;
+ }
+
+ syslog(LOG_INFO,
+ "audit_triggers() called within auditd with flags = %d\n",
+ flags);
+ /*
+ * XXX Message processing is done here
+ */
+ dirent = TAILQ_FIRST(&dir_q);
+ if(flags == AUDIT_TRIGGER_LOW_SPACE) {
+ if(dirent && (dirent->softlim != 1)) {
+ TAILQ_REMOVE(&dir_q, dirent, dirs);
+ /* add this node to the end of the list */
+ TAILQ_INSERT_TAIL(&dir_q, dirent, dirs);
+ audit_warn_soft(dirent->dirname);
+ dirent->softlim = 1;
+
+ if (TAILQ_NEXT(TAILQ_FIRST(&dir_q), dirs) != NULL && swap_audit_file() == -1) {
+ syslog(LOG_ERR, "Error swapping audit file\n");
+ }
+
+ /*
+ * check if the next dir has already reached its
+ * soft limit
+ */
+ dirent = TAILQ_FIRST(&dir_q);
+ if(dirent->softlim == 1) {
+ /* all dirs have reached their soft limit */
+ audit_warn_allsoft();
+ }
+ }
+ else {
+ /*
+ * Continue auditing to the current file
+ * Also generate an allsoft warning
+ * XXX do we want to do this ?
+ */
+ audit_warn_allsoft();
+ }
+ }
+ else if (flags == AUDIT_TRIGGER_FILE_FULL) {
+
+ /* delete current dir, go on to next */
+ TAILQ_REMOVE(&dir_q, dirent, dirs);
+ audit_warn_hard(dirent->dirname);
+ free(dirent->dirname);
+ free(dirent);
+
+ if(swap_audit_file() == -1) {
+ syslog(LOG_ERR, "Error swapping audit file in response to AUDIT_TRIGGER_FILE_FULL message\n");
+
+ /* Nowhere to write to */
+ audit_warn_allhard(++allhardcount);
+ }
+ }
+ return KERN_SUCCESS;
+}
+
+/*
+ * Reap our children.
+ */
+static void
+reap_children(void)
+{
+ pid_t child;
+ int wstatus;
+
+ while ((child = waitpid(-1, &wstatus, WNOHANG)) > 0) {
+ if (wstatus) {
+ syslog(LOG_INFO, "warn process [pid=%d] %s %d.\n", child,
+ ((WIFEXITED(wstatus)) ?
+ "exited with non-zero status" :
+ "exited as a result of signal"),
+ ((WIFEXITED(wstatus)) ?
+ WEXITSTATUS(wstatus) :
+ WTERMSIG(wstatus)));
+ }
+ }
+}
+
+/*
+ * Handle an RPC call
+ */
+boolean_t auditd_combined_server(
+ mach_msg_header_t *InHeadP,
+ mach_msg_header_t *OutHeadP)
+{
+ mach_port_t local_port = InHeadP->msgh_local_port;
+
+ if (local_port == signal_port) {
+ int signo = InHeadP->msgh_id;
+ int ret;
+
+ if (SIGTERM == signo) {
+ ret = close_all();
+ exit (ret);
+ } else if (SIGCHLD == signo) {
+ reap_children();
+ return TRUE;
+ } else {
+ syslog(LOG_INFO, "Recevied signal %d.\n", signo);
+ return TRUE;
+ }
+ } else if (local_port == control_port) {
+ boolean_t result;
+
+ result = audit_triggers_server(InHeadP, OutHeadP);
+ if (!result)
+ result = auditd_control_server(InHeadP, OutHeadP);
+ return result;
+ }
+ syslog(LOG_INFO, "Recevied msg on bad port 0x%x.\n", local_port);
+ return FALSE;
+}
+
+void wait_on_audit_trigger(port_set)
+ mach_port_t port_set;
+{
+ kern_return_t result;
+ result = mach_msg_server(auditd_combined_server, 4096, port_set, MACH_MSG_OPTION_NONE);
+ syslog(LOG_ERR, "abnormal exit\n");
+}
+
+/*
+ * Configure the audit controls in the kernel: the event to class mapping,
+ * kernel preselection mask, etc.
+ */
+int config_audit_controls(long flags)
+{
+ au_event_ent_t *ev;
+ au_evclass_map_t evc_map;
+ au_mask_t aumask;
+ int ctr = 0;
+ char naeventstr[NA_EVENT_STR_SIZE];
+
+ /* Process the audit event file, obtaining a class mapping for each
+ * event, and send that mapping into the kernel.
+ * XXX There's a risk here that the BSM library will return NULL
+ * for an event when it can't properly map it to a class. In that
+ * case, we will not process any events beyond the one that failed,
+ * but should. We need a way to get a count of the events.
+ */
+
+ setauevent();
+ while((ev = getauevent()) != NULL) {
+ evc_map.ec_number = ev->ae_number;
+ evc_map.ec_class = ev->ae_class;
+ if (auditon(A_SETCLASS, &evc_map, sizeof(au_evclass_map_t)) != 0) {
+ syslog(LOG_ERR,
+ "Failed to register class mapping for event %s",
+ ev->ae_name);
+ } else {
+ ctr++;
+ }
+ free(ev->ae_name);
+ free(ev->ae_desc);
+ free(ev);
+ }
+ endauevent();
+ if (ctr == 0)
+ syslog(LOG_ERR, "No events to class mappings registered.");
+ else
+ syslog(LOG_INFO, "Registered %d event to class mappings.", ctr);
+
+ /* Get the non-attributable event string and set the kernel mask
+ * from that.
+ */
+ if ((getacna(naeventstr, NA_EVENT_STR_SIZE) == 0)
+ && ( getauditflagsbin(naeventstr, &aumask) == 0)) {
+
+ if (auditon(A_SETKMASK, &aumask, sizeof(au_mask_t))){
+ syslog(LOG_ERR,
+ "Failed to register non-attributable event mask.");
+ } else {
+ syslog(LOG_INFO, "Registered non-attributable event mask.");
+ }
+
+ } else {
+ syslog(LOG_ERR,"Failed to obtain non-attributable event mask.");
+ }
+
+ /*
+ * Set the audit policy flags based on passed in parameter values.
+ */
+ if (auditon(A_SETPOLICY, &flags, sizeof(flags))) {
+ syslog(LOG_ERR,
+ "Failed to set audit policy.");
+ }
+
+ return 0;
+}
+
+void setup(long flags)
+{
+ mach_msg_type_name_t poly;
+ int aufd;
+ token_t *tok;
+
+ /* Allocate a port set */
+ if (mach_port_allocate(mach_task_self(),
+ MACH_PORT_RIGHT_PORT_SET,
+ &port_set) != KERN_SUCCESS) {
+ syslog(LOG_ERR, "allocation of port set failed\n");
+ fail_exit();
+ }
+
+ /* Allocate a signal reflection port */
+ if (mach_port_allocate(mach_task_self(),
+ MACH_PORT_RIGHT_RECEIVE,
+ &signal_port) != KERN_SUCCESS ||
+ mach_port_move_member(mach_task_self(),
+ signal_port,
+ port_set) != KERN_SUCCESS) {
+ syslog(LOG_ERR, "allocation of signal port failed\n");
+ fail_exit();
+ }
+
+ /* Allocate a trigger port */
+ if (mach_port_allocate(mach_task_self(),
+ MACH_PORT_RIGHT_RECEIVE,
+ &control_port) != KERN_SUCCESS ||
+ mach_port_move_member(mach_task_self(),
+ control_port,
+ port_set) != KERN_SUCCESS) {
+ syslog(LOG_ERR, "allocation of trigger port failed\n");
+ fail_exit();
+ }
+
+ /* create a send right on our trigger port */
+ mach_port_extract_right(mach_task_self(), control_port,
+ MACH_MSG_TYPE_MAKE_SEND, &control_port, &poly);
+
+ TAILQ_INIT(&dir_q);
+
+ /* register the trigger port with the kernel */
+ if(host_set_audit_control_port(mach_host_self(), control_port) != KERN_SUCCESS) {
+ syslog(LOG_ERR, "Cannot set Mach control port\n");
+ fail_exit();
+ }
+ else {
+ syslog(LOG_ERR, "Mach control port registered\n");
+ }
+
+ if(read_control_file() == -1) {
+ syslog(LOG_ERR, "Error reading control file\n");
+ fail_exit();
+ }
+
+ /* Generate an audit record */
+ if((aufd = au_open()) == -1) {
+ syslog(LOG_ERR, "Could not create audit startup event.\n");
+ } else {
+
+ if((tok = au_to_text("auditd::Audit startup")) != NULL) {
+ au_write(aufd, tok);
+ }
+
+ if(au_close(aufd, 1, AUE_audit_startup) == -1) {
+ syslog(LOG_ERR, "Could not close audit startup event.\n");
+ }
+ }
+
+ if (config_audit_controls(flags) == 0)
+ syslog(LOG_INFO, "Initialization successful\n");
+ else
+ syslog(LOG_INFO, "Initialization failed\n");
+}
+
+
+int main(int argc, char **argv)
+{
+ char ch;
+ long flags = AUDIT_CNT;
+ int debug = 0;
+
+ while ((ch = getopt(argc, argv, "dhs")) != -1) {
+ switch(ch) {
+
+ /* debug option */
+ case 'd':
+ debug = 1;
+ break;
+
+ /* fail-stop option */
+ case 's':
+ flags &= ~(AUDIT_CNT);
+ break;
+
+ /* halt-stop option */
+ case 'h':
+ flags |= AUDIT_AHLT;
+ break;
+
+ case '?':
+ default:
+ (void)fprintf(stderr,
+ "usage: auditd [-h | -s]\n");
+ exit(1);
+ }
+ }
+
+ openlog("auditd", LOG_CONS | LOG_PID, LOG_DAEMON);
+ syslog(LOG_INFO, "starting...\n");
+
+ if (debug == 0 && daemon(0, 0) == -1) {
+ syslog(LOG_ERR, "Failed to daemonize\n");
+ exit(1);
+ }
+
+ if(register_daemon() == -1) {
+ syslog(LOG_ERR, "Could not register as daemon\n");
+ exit(1);
+ }
+
+ setup(flags);
+ wait_on_audit_trigger(port_set);
+ syslog(LOG_INFO, "exiting.\n");
+
+ exit(1);
+}