#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/time.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
TAILQ_HEAD(, dir_ent) dir_q;
void fail_exit()
{
audit_warn_nostart();
exit(1);
}
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);
}
}
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;
}
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;
}
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);
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;
}
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);
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 {
close_lastfile(TS);
lastfile = fn;
return 0;
}
audit_warn_getacdir(dirent->dirname);
TAILQ_REMOVE(&dir_q, dirent, dirs);
free(dirent->dirname);
free(dirent);
}
return -1;
}
int read_control_file()
{
char cur_dir[MAX_DIR_SIZE];
struct dir_ent *dirent;
au_qctrl_t qctrl;
free_dir_q();
endac();
notify_post(__BSM_INTERNAL_NOTIFY_KEY);
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");
return -1;
}
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;
}
int close_all()
{
int err_ret = 0;
char TS[POSTFIX_LEN];
int aufd;
token_t *tok;
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");
}
}
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);
}
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);
}
int register_daemon()
{
FILE * pidfile;
int fd;
pid_t pid;
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;
}
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) {
fail_exit();
}
fflush(pidfile);
return 0;
}
kern_return_t auditd_control(auditd_port, flags)
mach_port_t auditd_port;
int flags;
{
int err_ret = 0;
switch(flags) {
case OPEN_NEW :
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;
}
#define DUPLICATE_INTERVAL 30
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;
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);
dirent = TAILQ_FIRST(&dir_q);
if(flags == AUDIT_TRIGGER_LOW_SPACE) {
if(dirent && (dirent->softlim != 1)) {
TAILQ_REMOVE(&dir_q, dirent, dirs);
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");
}
dirent = TAILQ_FIRST(&dir_q);
if(dirent->softlim == 1) {
audit_warn_allsoft();
}
}
else {
audit_warn_allsoft();
}
}
else if (flags == AUDIT_TRIGGER_FILE_FULL) {
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");
audit_warn_allhard(++allhardcount);
}
}
return KERN_SUCCESS;
}
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)));
}
}
}
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");
}
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];
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);
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.");
}
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;
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();
}
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();
}
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();
}
mach_port_extract_right(mach_task_self(), control_port,
MACH_MSG_TYPE_MAKE_SEND, &control_port, &poly);
TAILQ_INIT(&dir_q);
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();
}
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) {
case 'd':
debug = 1;
break;
case 's':
flags &= ~(AUDIT_CNT);
break;
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);
}