X-Git-Url: https://git.saurik.com/apple/system_cmds.git/blobdiff_plain/83f6dbe8135dc38ce4ac497ebea7f0ebc87d9199..2fc1e207eccd68aee093ed29dae719e77bac17dd:/auditd.tproj/auditd.c?ds=inline diff --git a/auditd.tproj/auditd.c b/auditd.tproj/auditd.c new file mode 100644 index 0000000..459bee3 --- /dev/null +++ b/auditd.tproj/auditd.c @@ -0,0 +1,791 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#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); +}