diff options
Diffstat (limited to 'framework/src/audit/src/auditd-event.c')
-rw-r--r-- | framework/src/audit/src/auditd-event.c | 1407 |
1 files changed, 1407 insertions, 0 deletions
diff --git a/framework/src/audit/src/auditd-event.c b/framework/src/audit/src/auditd-event.c new file mode 100644 index 00000000..ed0272e2 --- /dev/null +++ b/framework/src/audit/src/auditd-event.c @@ -0,0 +1,1407 @@ +/* auditd-event.c -- + * Copyright 2004-08,2011,2013 Red Hat Inc., Durham, North Carolina. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: + * Steve Grubb <sgrubb@redhat.com> + * + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <pthread.h> +#include <signal.h> +#include <fcntl.h> /* O_NOFOLLOW needs gnu defined */ +#include <sys/stat.h> +#include <errno.h> +#include <string.h> +#include <sys/time.h> +#include <sys/vfs.h> +#include <limits.h> /* POSIX_HOST_NAME_MAX */ +#include "auditd-event.h" +#include "auditd-dispatch.h" +#include "auditd-listen.h" +#include "libaudit.h" +#include "private.h" + +/* This is defined in auditd.c */ +extern volatile int stop; + +struct auditd_consumer_data { + struct daemon_conf *config; + pthread_mutex_t queue_lock; + pthread_cond_t queue_nonempty; + struct auditd_reply_list *head; + struct auditd_reply_list *tail; + int log_fd; + FILE *log_file; +}; + +/* Local function prototypes */ +static void *event_thread_main(void *arg); +static void handle_event(struct auditd_consumer_data *data); +static void write_to_log(const char *buf, struct auditd_consumer_data *data); +static void check_log_file_size(struct auditd_consumer_data *data); +static void check_space_left(int lfd, struct auditd_consumer_data *data); +static void do_space_left_action(struct auditd_consumer_data *data, int admin); +static void do_disk_full_action(struct auditd_consumer_data *data); +static void do_disk_error_action(const char *func, struct daemon_conf *config, + int err); +static void check_excess_logs(struct auditd_consumer_data *data); +static void rotate_logs_now(struct auditd_consumer_data *data); +static void rotate_logs(struct auditd_consumer_data *data, + unsigned int num_logs); +static void shift_logs(struct auditd_consumer_data *data); +static int open_audit_log(struct auditd_consumer_data *data); +static void change_runlevel(const char *level); +static void safe_exec(const char *exe); +static char *format_raw(const struct audit_reply *rep, + struct daemon_conf *config); +static void reconfigure(struct auditd_consumer_data *data); + + +/* Local Data */ +static struct auditd_consumer_data consumer_data; +static pthread_t event_thread; +static unsigned int disk_err_warning = 0; +static int fs_space_warning = 0; +static int fs_admin_space_warning = 0; +static int fs_space_left = 1; +static int logging_suspended = 0; +static const char *SINGLE = "1"; +static const char *HALT = "0"; +static char *format_buf = NULL; +static off_t log_size = 0; + + +void shutdown_events(void) +{ + /* Give it 5 seconds to clear the queue */ + alarm(5); + pthread_join(event_thread, NULL); + free((void *)format_buf); + fclose(consumer_data.log_file); +} + +int init_event(struct daemon_conf *config) +{ + /* Store the netlink descriptor and config info away */ + consumer_data.config = config; + consumer_data.log_fd = -1; + + /* Setup IPC mechanisms */ + pthread_mutex_init(&consumer_data.queue_lock, NULL); + pthread_cond_init(&consumer_data.queue_nonempty, NULL); + + /* Reset the queue */ + consumer_data.head = consumer_data.tail = NULL; + + /* Now open the log */ + if (config->daemonize == D_BACKGROUND) { + if (open_audit_log(&consumer_data)) + return 1; + } else { + consumer_data.log_fd = 1; // stdout + consumer_data.log_file = fdopen(consumer_data.log_fd, "a"); + if (consumer_data.log_file == NULL) { + audit_msg(LOG_ERR, + "Error setting up stdout descriptor (%s)", + strerror(errno)); + return 1; + } + /* Set it to line buffering */ + setlinebuf(consumer_data.log_file); + } + + /* Create the worker thread */ + if (pthread_create(&event_thread, NULL, + event_thread_main, &consumer_data) < 0) { + audit_msg(LOG_ERR, "Couldn't create event thread, exiting"); + fclose(consumer_data.log_file); + return 1; + } + + if (config->daemonize == D_BACKGROUND) { + check_log_file_size(&consumer_data); + check_excess_logs(&consumer_data); + check_space_left(consumer_data.log_fd, &consumer_data); + } + format_buf = (char *)malloc(MAX_AUDIT_MESSAGE_LENGTH + + _POSIX_HOST_NAME_MAX); + if (format_buf == NULL) { + audit_msg(LOG_ERR, "No memory for formatting, exiting"); + fclose(consumer_data.log_file); + return 1; + } + return 0; +} + +/* This function takes a malloc'd rep and places it on the queue. The + dequeue'r is responsible for freeing the memory. */ +void enqueue_event(struct auditd_reply_list *rep) +{ + char *buf = NULL; + int len; + + rep->ack_func = 0; + rep->ack_data = 0; + rep->sequence_id = 0; + + if (rep->reply.type != AUDIT_DAEMON_RECONFIG) { + switch (consumer_data.config->log_format) + { + case LF_RAW: + buf = format_raw(&rep->reply, consumer_data.config); + break; + case LF_NOLOG: + // We need the rotate event to get enqueued + if (rep->reply.type != AUDIT_DAEMON_ROTATE ) { + // Internal DAEMON messages should be free'd + if (rep->reply.type >= AUDIT_FIRST_DAEMON && + rep->reply.type <= AUDIT_LAST_DAEMON) + free((void *)rep->reply.message); + free(rep); + return; + } + break; + default: + audit_msg(LOG_ERR, + "Illegal log format detected %d", + consumer_data.config->log_format); + // Internal DAEMON messages should be free'd + if (rep->reply.type >= AUDIT_FIRST_DAEMON && + rep->reply.type <= AUDIT_LAST_DAEMON) + free((void *)rep->reply.message); + free(rep); + return; + } + + if (buf) { + len = strlen(buf); + if (len < MAX_AUDIT_MESSAGE_LENGTH - 1) + memcpy(rep->reply.msg.data, buf, len+1); + else { + // FIXME: is truncation the right thing to do? + memcpy(rep->reply.msg.data, buf, + MAX_AUDIT_MESSAGE_LENGTH-1); + rep->reply.msg.data[MAX_AUDIT_MESSAGE_LENGTH-1] = 0; + } + } + } + + rep->next = NULL; /* new packet goes at end - so zero this */ + + pthread_mutex_lock(&consumer_data.queue_lock); + if (consumer_data.head == NULL) { + consumer_data.head = consumer_data.tail = rep; + pthread_cond_signal(&consumer_data.queue_nonempty); + } else { + /* FIXME: wait for room on the queue */ + + /* OK there's room...add it in */ + consumer_data.tail->next = rep; /* link in at end */ + consumer_data.tail = rep; /* move end to newest */ + } + pthread_mutex_unlock(&consumer_data.queue_lock); +} + +/* This function takes a preformatted message and places it on the + queue. The dequeue'r is responsible for freeing the memory. */ +void enqueue_formatted_event(char *msg, ack_func_type ack_func, void *ack_data, uint32_t sequence_id) +{ + int len; + struct auditd_reply_list *rep; + + rep = (struct auditd_reply_list *) calloc (1, sizeof (*rep)); + if (rep == NULL) { + audit_msg(LOG_ERR, "Cannot allocate audit reply"); + return; + } + + rep->ack_func = ack_func; + rep->ack_data = ack_data; + rep->sequence_id = sequence_id; + + len = strlen (msg); + if (len < MAX_AUDIT_MESSAGE_LENGTH - 1) + memcpy (rep->reply.msg.data, msg, len+1); + else { + /* FIXME: is truncation the right thing to do? */ + memcpy (rep->reply.msg.data, msg, MAX_AUDIT_MESSAGE_LENGTH-1); + rep->reply.msg.data[MAX_AUDIT_MESSAGE_LENGTH-1] = 0; + } + + pthread_mutex_lock(&consumer_data.queue_lock); + if (consumer_data.head == NULL) { + consumer_data.head = consumer_data.tail = rep; + pthread_cond_signal(&consumer_data.queue_nonempty); + } else { + /* FIXME: wait for room on the queue */ + + /* OK there's room...add it in */ + consumer_data.tail->next = rep; /* link in at end */ + consumer_data.tail = rep; /* move end to newest */ + } + pthread_mutex_unlock(&consumer_data.queue_lock); +} + +void resume_logging(void) +{ + logging_suspended = 0; + fs_space_left = 1; + disk_err_warning = 0; + fs_space_warning = 0; + fs_admin_space_warning = 0; + audit_msg(LOG_ERR, "Audit daemon is attempting to resume logging."); +} + +static void *event_thread_main(void *arg) +{ + struct auditd_consumer_data *data = arg; + sigset_t sigs; + + /* This is a worker thread. Don't handle signals. */ + sigemptyset(&sigs); + sigaddset(&sigs, SIGALRM); + sigaddset(&sigs, SIGTERM); + sigaddset(&sigs, SIGHUP); + sigaddset(&sigs, SIGUSR1); + sigaddset(&sigs, SIGUSR2); + pthread_sigmask(SIG_SETMASK, &sigs, NULL); + + while (1) { + struct auditd_reply_list *cur; + int stop_req = 0; +// FIXME: wait for data + pthread_mutex_lock(&data->queue_lock); + while (data->head == NULL) { + pthread_cond_wait(&data->queue_nonempty, + &data->queue_lock); + } +// FIXME: at this point we can use data->head unlocked since it won't change. + handle_event(data); + cur = data->head; +// FIXME: relock at this point + if (data->tail == data->head) + data->tail = NULL; + data->head = data->head->next; + if (data->head == NULL && stop && + ( cur->reply.type == AUDIT_DAEMON_END || + cur->reply.type == AUDIT_DAEMON_ABORT) ) + stop_req = 1; + pthread_mutex_unlock(&data->queue_lock); + + /* Internal DAEMON messages should be free'd */ + if (cur->reply.type >= AUDIT_FIRST_DAEMON && + cur->reply.type <= AUDIT_LAST_DAEMON) { + free((void *)cur->reply.message); + } + free(cur); + if (stop_req) + break; + } + return NULL; +} + + +/* This function takes the newly dequeued event and handles it. */ +static unsigned int count = 0L; +static void handle_event(struct auditd_consumer_data *data) +{ + char *buf = data->head->reply.msg.data; + + if (data->head->reply.type == AUDIT_DAEMON_RECONFIG) { + reconfigure(data); + switch (consumer_data.config->log_format) + { + case LF_RAW: + buf = format_raw(&data->head->reply, consumer_data.config); + break; + case LF_NOLOG: + return; + default: + audit_msg(LOG_ERR, + "Illegal log format detected %d", + consumer_data.config->log_format); + return; + } + } else if (data->head->reply.type == AUDIT_DAEMON_ROTATE) { + rotate_logs_now(data); + if (consumer_data.config->log_format == LF_NOLOG) + return; + } + if (!logging_suspended) { + + write_to_log(buf, data); + + /* See if we need to flush to disk manually */ + if (data->config->flush == FT_INCREMENTAL) { + count++; + if ((count % data->config->freq) == 0) { + int rc; + errno = 0; + do { + rc = fflush(data->log_file); + } while (rc < 0 && errno == EINTR); + if (errno) { + if (errno == ENOSPC && + fs_space_left == 1) { + fs_space_left = 0; + do_disk_full_action(data); + } else + //EIO is only likely failure mode + do_disk_error_action("flush", + data->config, errno); + } + + /* EIO is only likely failure mode */ + if ((data->config->daemonize == D_BACKGROUND)&& + (fsync(data->log_fd) != 0)) { + do_disk_error_action("fsync", + data->config, errno); + } + } + } + } +} + +static void send_ack(struct auditd_consumer_data *data, int ack_type, + const char *msg) +{ + if (data->head->ack_func) { + unsigned char header[AUDIT_RMW_HEADER_SIZE]; + + AUDIT_RMW_PACK_HEADER(header, 0, ack_type, strlen(msg), + data->head->sequence_id); + + data->head->ack_func(data->head->ack_data, header, msg); + } +} + +/* This function writes the given buf to the current log file */ +static void write_to_log(const char *buf, struct auditd_consumer_data *data) +{ + int rc; + FILE *f = data->log_file; + struct daemon_conf *config = data->config; + int ack_type = AUDIT_RMW_TYPE_ACK; + const char *msg = ""; + + /* write it to disk */ + rc = fprintf(f, "%s\n", buf); + + /* error? Handle it */ + if (rc < 0) { + if (errno == ENOSPC) { + ack_type = AUDIT_RMW_TYPE_DISKFULL; + msg = "disk full"; + send_ack(data, ack_type, msg); + if (fs_space_left == 1) { + fs_space_left = 0; + do_disk_full_action(data); + } + } else { + int saved_errno = errno; + ack_type = AUDIT_RMW_TYPE_DISKERROR; + msg = "disk write error"; + send_ack(data, ack_type, msg); + do_disk_error_action("write", config, saved_errno); + } + } else { + /* check log file size & space left on partition */ + if (config->daemonize == D_BACKGROUND) { + // If either of these fail, I consider it an + // inconvenience as opposed to something that is + // actionable. There may be some temporary condition + // that the system recovers from. The real error + // occurs on write. + log_size += rc; + check_log_file_size(data); + check_space_left(data->log_fd, data); + } + + if (fs_space_warning) + ack_type = AUDIT_RMW_TYPE_DISKLOW; + send_ack(data, ack_type, msg); + disk_err_warning = 0; + } +} + +static void check_log_file_size(struct auditd_consumer_data *data) +{ + struct daemon_conf *config = data->config; + + /* did we cross the size limit? */ + off_t sz = log_size / MEGABYTE; + + if (sz >= config->max_log_size && (config->daemonize == D_BACKGROUND)) { + switch (config->max_log_size_action) + { + case SZ_IGNORE: + break; + case SZ_SYSLOG: + audit_msg(LOG_ERR, + "Audit daemon log file is larger than max size"); + break; + case SZ_SUSPEND: + audit_msg(LOG_ERR, + "Audit daemon is suspending logging due to logfile size."); + logging_suspended = 1; + break; + case SZ_ROTATE: + if (data->config->num_logs > 1) { + audit_msg(LOG_NOTICE, + "Audit daemon rotating log files"); + rotate_logs(data, 0); + } + break; + case SZ_KEEP_LOGS: + audit_msg(LOG_NOTICE, + "Audit daemon rotating log files with keep option"); + shift_logs(data); + break; + default: + audit_msg(LOG_ALERT, + "Audit daemon log file is larger than max size and unknown action requested"); + break; + } + } +} + +static void check_space_left(int lfd, struct auditd_consumer_data *data) +{ + int rc; + struct statfs buf; + struct daemon_conf *config = data->config; + + rc = fstatfs(lfd, &buf); + if (rc == 0) { + if (buf.f_bavail < 5) { + /* we won't consume the last 5 blocks */ + fs_space_left = 0; + do_disk_full_action(data); + } else { + unsigned long blocks; + unsigned long block_size = buf.f_bsize; + blocks = config->space_left * (MEGABYTE/block_size); + if (buf.f_bavail < blocks) { + if (fs_space_warning == 0) { + do_space_left_action(data, 0); + fs_space_warning = 1; + } + } else if (fs_space_warning && + config->space_left_action == FA_SYSLOG){ + // Auto reset only if failure action is syslog + fs_space_warning = 0; + } + blocks=config->admin_space_left * (MEGABYTE/block_size); + if (buf.f_bavail < blocks) { + if (fs_admin_space_warning == 0) { + do_space_left_action(data, 1); + fs_admin_space_warning = 1; + } + } else if (fs_admin_space_warning && + config->admin_space_left_action == FA_SYSLOG) { + // Auto reset only if failure action is syslog + fs_admin_space_warning = 0; + } + } + } + else audit_msg(LOG_DEBUG, "fstatfs returned:%d, %s", rc, + strerror(errno)); +} + +extern int sendmail(const char *subject, const char *content, + const char *mail_acct); +static void do_space_left_action(struct auditd_consumer_data *data, int admin) +{ + int action; + struct daemon_conf *config = data->config; + + if (admin) + action = config->admin_space_left_action; + else + action = config->space_left_action; + + switch (action) + { + case FA_IGNORE: + break; + case FA_SYSLOG: + audit_msg(LOG_ALERT, + "Audit daemon is low on disk space for logging"); + break; + case FA_ROTATE: + if (config->num_logs > 1) { + audit_msg(LOG_NOTICE, + "Audit daemon rotating log files"); + rotate_logs(data, 0); + } + break; + case FA_EMAIL: + if (admin == 0) { + sendmail("Audit Disk Space Alert", + "The audit daemon is low on disk space for logging! Please take action\nto ensure no loss of service.", + config->action_mail_acct); + audit_msg(LOG_ALERT, + "Audit daemon is low on disk space for logging"); + } else { + sendmail("Audit Admin Space Alert", + "The audit daemon is very low on disk space for logging! Immediate action\nis required to ensure no loss of service.", + config->action_mail_acct); + audit_msg(LOG_ALERT, + "Audit daemon is very low on disk space for logging"); + } + break; + case FA_EXEC: + if (admin) + safe_exec(config->admin_space_left_exe); + else + safe_exec(config->space_left_exe); + break; + case FA_SUSPEND: + audit_msg(LOG_ALERT, + "Audit daemon is suspending logging due to low disk space."); + logging_suspended = 1; + break; + case FA_SINGLE: + audit_msg(LOG_ALERT, + "The audit daemon is now changing the system to single user mode"); + change_runlevel(SINGLE); + break; + case FA_HALT: + audit_msg(LOG_ALERT, + "The audit daemon is now halting the system"); + change_runlevel(HALT); + break; + default: + audit_msg(LOG_ALERT, + "Audit daemon is low on disk space for logging and unknown action requested"); + break; + } +} + +static void do_disk_full_action(struct auditd_consumer_data *data) +{ + struct daemon_conf *config = data->config; + + audit_msg(LOG_ALERT, + "Audit daemon has no space left on logging partition"); + switch (config->disk_full_action) + { + case FA_IGNORE: + case FA_SYSLOG: /* Message is syslogged above */ + break; + case FA_ROTATE: + if (config->num_logs > 1) { + audit_msg(LOG_NOTICE, + "Audit daemon rotating log files"); + rotate_logs(data, 0); + } + break; + case FA_EXEC: + safe_exec(config->disk_full_exe); + break; + case FA_SUSPEND: + audit_msg(LOG_ALERT, + "Audit daemon is suspending logging due to no space left on logging partition."); + logging_suspended = 1; + break; + case FA_SINGLE: + audit_msg(LOG_ALERT, + "The audit daemon is now changing the system to single user mode due to no space left on logging partition"); + change_runlevel(SINGLE); + break; + case FA_HALT: + audit_msg(LOG_ALERT, + "The audit daemon is now halting the system due to no space left on logging partition"); + change_runlevel(HALT); + break; + default: + audit_msg(LOG_ALERT, "Unknown disk full action requested"); + break; + } +} + +static void do_disk_error_action(const char * func, struct daemon_conf *config, + int err) +{ + char text[128]; + + switch (config->disk_error_action) + { + case FA_IGNORE: + break; + case FA_SYSLOG: + if (disk_err_warning < 5) { + snprintf(text, sizeof(text), + "%s: Audit daemon detected an error writing an event to disk (%s)", + func, strerror(err)); + audit_msg(LOG_ALERT, "%s", text); + disk_err_warning++; + } + break; + case FA_EXEC: + safe_exec(config->disk_error_exe); + break; + case FA_SUSPEND: + audit_msg(LOG_ALERT, + "Audit daemon is suspending logging due to previously mentioned write error"); + logging_suspended = 1; + break; + case FA_SINGLE: + audit_msg(LOG_ALERT, + "The audit daemon is now changing the system to single user mode due to previously mentioned write error"); + change_runlevel(SINGLE); + break; + case FA_HALT: + audit_msg(LOG_ALERT, + "The audit daemon is now halting the system due to previously mentioned write error."); + change_runlevel(HALT); + break; + default: + audit_msg(LOG_ALERT, + "Unknown disk error action requested"); + break; + } +} + +static void rotate_logs_now(struct auditd_consumer_data *data) +{ + struct daemon_conf *config = data->config; + + if (config->max_log_size_action == SZ_KEEP_LOGS) + shift_logs(data); + else + rotate_logs(data, 0); +} + +/* Check for and remove excess logs so that we don't run out of room */ +static void check_excess_logs(struct auditd_consumer_data *data) +{ + int rc; + unsigned int i, len; + char *name; + + // Only do this if rotate is the log size action + // and we actually have a limit + if (data->config->max_log_size_action != SZ_ROTATE || + data->config->num_logs < 2) + return; + + len = strlen(data->config->log_file) + 16; + name = (char *)malloc(len); + if (name == NULL) { /* Not fatal - just messy */ + audit_msg(LOG_ERR, "No memory checking excess logs"); + return; + } + + // We want 1 beyond the normal logs + i=data->config->num_logs; + rc=0; + while (rc == 0) { + snprintf(name, len, "%s.%d", data->config->log_file, i++); + rc=unlink(name); + if (rc == 0) + audit_msg(LOG_NOTICE, + "Log %s removed as it exceeds num_logs parameter", + name); + } + free(name); +} + +static void rotate_logs(struct auditd_consumer_data *data, + unsigned int num_logs) +{ + int rc; + unsigned int len, i; + char *oldname, *newname; + + if (data->config->max_log_size_action == SZ_ROTATE && + data->config->num_logs < 2) + return; + + /* Close audit file. fchmod and fchown errors are not fatal because we + * already adjusted log file permissions and ownership when opening the + * log file. */ + if (fchmod(data->log_fd, data->config->log_group ? S_IRUSR|S_IRGRP : + S_IRUSR) < 0) { + audit_msg(LOG_NOTICE, "Couldn't change permissions while " + "rotating log file (%s)", strerror(errno)); + } + if (fchown(data->log_fd, 0, data->config->log_group) < 0) { + audit_msg(LOG_NOTICE, "Couldn't change ownership while " + "rotating log file (%s)", strerror(errno)); + } + fclose(data->log_file); + + /* Rotate */ + len = strlen(data->config->log_file) + 16; + oldname = (char *)malloc(len); + if (oldname == NULL) { /* Not fatal - just messy */ + audit_msg(LOG_ERR, "No memory rotating logs"); + logging_suspended = 1; + return; + } + newname = (char *)malloc(len); + if (newname == NULL) { /* Not fatal - just messy */ + audit_msg(LOG_ERR, "No memory rotating logs"); + free(oldname); + logging_suspended = 1; + return; + } + + /* If we are rotating, get number from config */ + if (num_logs == 0) + num_logs = data->config->num_logs; + + /* Handle this case first since it will not enter the for loop */ + if (num_logs == 2) + snprintf(oldname, len, "%s.1", data->config->log_file); + + for (i=num_logs - 1; i>1; i--) { + snprintf(oldname, len, "%s.%d", data->config->log_file, i-1); + snprintf(newname, len, "%s.%d", data->config->log_file, i); + /* if the old file exists */ + rc = rename(oldname, newname); + if (rc == -1 && errno != ENOENT) { + // Likely errors: ENOSPC, ENOMEM, EBUSY + int saved_errno = errno; + audit_msg(LOG_ERR, + "Error rotating logs from %s to %s (%s)", + oldname, newname, strerror(errno)); + if (saved_errno == ENOSPC && fs_space_left == 1) { + fs_space_left = 0; + do_disk_full_action(data); + } else + do_disk_error_action("rotate", data->config, + saved_errno); + } + } + free(newname); + + /* At this point, oldname should point to lowest number - use it */ + newname = oldname; + rc = rename(data->config->log_file, newname); + if (rc == -1 && errno != ENOENT) { + // Likely errors: ENOSPC, ENOMEM, EBUSY + int saved_errno = errno; + audit_msg(LOG_ERR, "Error rotating logs from %s to %s (%s)", + data->config->log_file, newname, strerror(errno)); + if (saved_errno == ENOSPC && fs_space_left == 1) { + fs_space_left = 0; + do_disk_full_action(data); + } else + do_disk_error_action("rotate2", data->config, + saved_errno); + + /* At this point, we've failed to rotate the original log. + * So, let's make the old log writable and try again next + * time */ + chmod(data->config->log_file, + data->config->log_group ? S_IWUSR|S_IRUSR|S_IRGRP : + S_IWUSR|S_IRUSR); + } + free(newname); + + /* open new audit file */ + if (open_audit_log(data)) { + int saved_errno = errno; + audit_msg(LOG_NOTICE, + "Could not reopen a log after rotating."); + logging_suspended = 1; + do_disk_error_action("reopen", data->config, saved_errno); + } +} + +static int last_log = 1; +static void shift_logs(struct auditd_consumer_data *data) +{ + // The way this has to work is to start scanning from .1 up until + // no file is found. Then do the rotate algorithm using that number + // instead of log_max. + unsigned int num_logs, len; + char *name; + + len = strlen(data->config->log_file) + 16; + name = (char *)malloc(len); + if (name == NULL) { /* Not fatal - just messy */ + audit_msg(LOG_ERR, "No memory shifting logs"); + return; + } + + // Find last log + num_logs = last_log; + while (num_logs) { + snprintf(name, len, "%s.%d", data->config->log_file, + num_logs); + if (access(name, R_OK) != 0) + break; + num_logs++; + } + + /* Our last known file disappeared, start over... */ + if (num_logs <= last_log && last_log > 1) { + audit_msg(LOG_WARNING, "Last known log disappeared (%s)", name); + num_logs = last_log = 1; + while (num_logs) { + snprintf(name, len, "%s.%d", data->config->log_file, + num_logs); + if (access(name, R_OK) != 0) + break; + num_logs++; + } + audit_msg(LOG_INFO, "Next log to use will be %s", name); + } + last_log = num_logs; + rotate_logs(data, num_logs+1); + free(name); +} + +/* + * This function handles opening a descriptor for the audit log + * file and ensuring the correct options are applied to the descriptor. + * It returns 0 on success and 1 on failure. + */ +static int open_audit_log(struct auditd_consumer_data *data) +{ + int flags, lfd; + + // Likely errors for open: Almost anything + // Likely errors on rotate: ENFILE, ENOMEM, ENOSPC +retry: + lfd = open(data->config->log_file, O_WRONLY|O_APPEND|O_NOFOLLOW); + if (lfd < 0) { + if (errno == ENOENT) { + lfd = create_log_file(data->config->log_file); + if (lfd < 0) { + audit_msg(LOG_ERR, + "Couldn't create log file %s (%s)", + data->config->log_file, + strerror(errno)); + return 1; + } + close(lfd); + lfd = open(data->config->log_file, + O_WRONLY|O_APPEND|O_NOFOLLOW); + log_size = 0; + } else if (errno == ENFILE) { + // All system descriptors used, try again... + goto retry; + } + if (lfd < 0) { + audit_msg(LOG_ERR, "Couldn't open log file %s (%s)", + data->config->log_file, strerror(errno)); + return 1; + } + } else { + // Get initial size + struct stat st; + + int rc = fstat(lfd, &st); + if (rc == 0) + log_size = st.st_size; + else { + close(lfd); + return 1; + } + } + + if (fcntl(lfd, F_SETFD, FD_CLOEXEC) == -1) { + close(lfd); + audit_msg(LOG_ERR, "Error setting log file CLOEXEC flag (%s)", + strerror(errno)); + return 1; + } + if (data->config->flush == FT_DATA) { + flags = fcntl(lfd, F_GETFL); + if (flags < 0) { + audit_msg(LOG_ERR, "Couldn't get log file flags (%s)", + strerror(errno)); + close(lfd); + return 1; + } + if (fcntl(lfd, F_SETFL, flags|O_DSYNC) < 0) { + audit_msg(LOG_ERR, + "Couldn't set data sync mode on log file (%s)", + strerror(errno)); + close(lfd); + return 1; + } + } + else if (data->config->flush == FT_SYNC){ + flags = fcntl(lfd, F_GETFL); + if (flags < 0) { + audit_msg(LOG_ERR, "Couldn't get log file flags (%s)", + strerror(errno)); + close(lfd); + return 1; + } + if (fcntl(lfd, F_SETFL, flags|O_SYNC) < 0) { + audit_msg(LOG_ERR, + "Couldn't set sync mode on log file (%s)", + strerror(errno)); + close(lfd); + return 1; + } + } + if (fchmod(lfd, data->config->log_group ? S_IRUSR|S_IWUSR|S_IRGRP : + S_IRUSR|S_IWUSR) < 0) { + audit_msg(LOG_ERR, + "Couldn't change permissions of log file (%s)", + strerror(errno)); + close(lfd); + return 1; + } + if (fchown(lfd, 0, data->config->log_group) < 0) { + audit_msg(LOG_ERR, "Couldn't change ownership of log file (%s)", + strerror(errno)); + close(lfd); + return 1; + } + + data->log_fd = lfd; + data->log_file = fdopen(lfd, "a"); + if (data->log_file == NULL) { + audit_msg(LOG_ERR, "Error setting up log descriptor (%s)", + strerror(errno)); + close(lfd); + return 1; + } + + /* Set it to line buffering */ + setlinebuf(consumer_data.log_file); + return 0; +} + +static void change_runlevel(const char *level) +{ + char *argv[3]; + int pid; + struct sigaction sa; + static const char *init_pgm = "/sbin/init"; + + pid = fork(); + if (pid < 0) { + audit_msg(LOG_ALERT, + "Audit daemon failed to fork switching runlevels"); + return; + } + if (pid) /* Parent */ + return; + /* Child */ + sigfillset (&sa.sa_mask); + sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0); + + argv[0] = (char *)init_pgm; + argv[1] = (char *)level; + argv[2] = NULL; + execve(init_pgm, argv, NULL); + audit_msg(LOG_ALERT, "Audit daemon failed to exec %s", init_pgm); + exit(1); +} + +static void safe_exec(const char *exe) +{ + char *argv[2]; + int pid; + struct sigaction sa; + + if (exe == NULL) { + audit_msg(LOG_ALERT, + "Safe_exec passed NULL for program to execute"); + return; + } + + pid = fork(); + if (pid < 0) { + audit_msg(LOG_ALERT, + "Audit daemon failed to fork doing safe_exec"); + return; + } + if (pid) /* Parent */ + return; + /* Child */ + sigfillset (&sa.sa_mask); + sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0); + + argv[0] = (char *)exe; + argv[1] = NULL; + execve(exe, argv, NULL); + audit_msg(LOG_ALERT, "Audit daemon failed to exec %s", exe); + exit(1); +} + +/* +* This function will take an audit structure and return a +* text buffer that's unformatted for writing to disk. If there +* is an error the return value is NULL. +*/ +static char *format_raw(const struct audit_reply *rep, + struct daemon_conf *config) +{ + char *ptr; + + if (rep==NULL) { + if (config->node_name_format != N_NONE) + snprintf(format_buf, MAX_AUDIT_MESSAGE_LENGTH + + _POSIX_HOST_NAME_MAX - 32, + "node=%s type=DAEMON msg=NULL reply", + config->node_name); + else + snprintf(format_buf, MAX_AUDIT_MESSAGE_LENGTH, + "type=DAEMON msg=NULL reply"); + } else { + int len, nlen; + const char *type, *message; + char unknown[32]; + type = audit_msg_type_to_name(rep->type); + if (type == NULL) { + snprintf(unknown, sizeof(unknown), + "UNKNOWN[%d]", rep->type); + type = unknown; + } + if (rep->message == NULL) { + message = "msg lost"; + len = 8; + } else { + message = rep->message; + len = rep->len; + } + + // Note: This can truncate messages if + // MAX_AUDIT_MESSAGE_LENGTH is too small + if (config->node_name_format != N_NONE) + nlen = snprintf(format_buf, MAX_AUDIT_MESSAGE_LENGTH + + _POSIX_HOST_NAME_MAX - 32, + "node=%s type=%s msg=%.*s\n", + config->node_name, type, len, message); + else + nlen = snprintf(format_buf, + MAX_AUDIT_MESSAGE_LENGTH - 32, + "type=%s msg=%.*s", type, len, message); + + /* Replace \n with space so it looks nicer. */ + ptr = format_buf; + while ((ptr = strchr(ptr, 0x0A)) != NULL) + *ptr = ' '; + + /* Trim trailing space off since it wastes space */ + if (format_buf[nlen-1] == ' ') + format_buf[nlen-1] = 0; + } + return format_buf; +} + +static void reconfigure(struct auditd_consumer_data *data) +{ + struct daemon_conf *nconf = data->head->reply.conf; + struct daemon_conf *oconf = data->config; + uid_t uid = nconf->sender_uid; + pid_t pid = nconf->sender_pid; + const char *ctx = nconf->sender_ctx; + struct timeval tv; + char txt[MAX_AUDIT_MESSAGE_LENGTH]; + char date[40]; + unsigned int seq_num; + int need_size_check = 0, need_reopen = 0, need_space_check = 0; + + snprintf(txt, sizeof(txt), + "config change requested by pid=%d auid=%u subj=%s", + pid, uid, ctx); + audit_msg(LOG_NOTICE, "%s", txt); + + /* Do the reconfiguring. These are done in a specific + * order from least invasive to most invasive. We will + * start with general system parameters. */ + + // start with disk error action. + oconf->disk_error_action = nconf->disk_error_action; + free((char *)oconf->disk_error_exe); + oconf->disk_error_exe = nconf->disk_error_exe; + disk_err_warning = 0; + + // numlogs is next + oconf->num_logs = nconf->num_logs; + + // flush freq + oconf->freq = nconf->freq; + + // priority boost + if (oconf->priority_boost != nconf->priority_boost) { + int rc; + + oconf->priority_boost = nconf->priority_boost; + errno = 0; + rc = nice(-oconf->priority_boost); + if (rc == -1 && errno) + audit_msg(LOG_NOTICE, "Cannot change priority in " + "reconfigure (%s)", strerror(errno)); + } + + // log format + oconf->log_format = nconf->log_format; + + // action_mail_acct + if (strcmp(oconf->action_mail_acct, nconf->action_mail_acct)) { + free((void *)oconf->action_mail_acct); + oconf->action_mail_acct = nconf->action_mail_acct; + } else + free((void *)nconf->action_mail_acct); + + // node_name + if (oconf->node_name_format != nconf->node_name_format || + (oconf->node_name && nconf->node_name && + strcmp(oconf->node_name, nconf->node_name) != 0)) { + oconf->node_name_format = nconf->node_name_format; + free((char *)oconf->node_name); + oconf->node_name = nconf->node_name; + } + + /* Now look at audit dispatcher changes */ + oconf->qos = nconf->qos; // dispatcher qos + + // do the dispatcher app change + if (oconf->dispatcher || nconf->dispatcher) { + // none before, start new one + if (oconf->dispatcher == NULL) { + oconf->dispatcher = strdup(nconf->dispatcher); + if (oconf->dispatcher == NULL) { + int saved_errno = errno; + audit_msg(LOG_NOTICE, + "Could not allocate dispatcher memory" + " in reconfigure"); + // Likely errors: ENOMEM + do_disk_error_action("reconfig", data->config, + saved_errno); + } + if(init_dispatcher(oconf)) {// dispatcher & qos is used + int saved_errno = errno; + audit_msg(LOG_NOTICE, + "Could not start dispatcher %s" + " in reconfigure", oconf->dispatcher); + // Likely errors: Socketpairs or exec perms + do_disk_error_action("reconfig", data->config, + saved_errno); + } + } + // have one, but none after this + else if (nconf->dispatcher == NULL) { + shutdown_dispatcher(); + free((char *)oconf->dispatcher); + oconf->dispatcher = NULL; + } + // they are different apps + else if (strcmp(oconf->dispatcher, nconf->dispatcher)) { + shutdown_dispatcher(); + free((char *)oconf->dispatcher); + oconf->dispatcher = strdup(nconf->dispatcher); + if (oconf->dispatcher == NULL) { + int saved_errno = errno; + audit_msg(LOG_NOTICE, + "Could not allocate dispatcher memory" + " in reconfigure"); + // Likely errors: ENOMEM + do_disk_error_action("reconfig", data->config, + saved_errno); + } + if(init_dispatcher(oconf)) {// dispatcher & qos is used + int saved_errno = errno; + audit_msg(LOG_NOTICE, + "Could not start dispatcher %s" + " in reconfigure", oconf->dispatcher); + // Likely errors: Socketpairs or exec perms + do_disk_error_action("reconfig", data->config, + saved_errno); + } + } + // they are the same app - just signal it + else { + reconfigure_dispatcher(oconf); + free((char *)nconf->dispatcher); + nconf->dispatcher = NULL; + } + } + + // network listener + auditd_tcp_listen_reconfigure(nconf, oconf); + + /* At this point we will work on the items that are related to + * a single log file. */ + + // max logfile action + if (oconf->max_log_size_action != nconf->max_log_size_action) { + oconf->max_log_size_action = nconf->max_log_size_action; + need_size_check = 1; + } + + // max log size + if (oconf->max_log_size != nconf->max_log_size) { + oconf->max_log_size = nconf->max_log_size; + need_size_check = 1; + } + + if (need_size_check) { + logging_suspended = 0; + check_log_file_size(data); + } + + // flush technique + if (oconf->flush != nconf->flush) { + oconf->flush = nconf->flush; + need_reopen = 1; + } + + // logfile + if (strcmp(oconf->log_file, nconf->log_file)) { + free((void *)oconf->log_file); + oconf->log_file = nconf->log_file; + need_reopen = 1; + need_space_check = 1; // might be on new partition + } else + free((void *)nconf->log_file); + + if (need_reopen) { + fclose(data->log_file); + if (open_audit_log(data)) { + int saved_errno = errno; + audit_msg(LOG_NOTICE, + "Could not reopen a log after reconfigure"); + logging_suspended = 1; + // Likely errors: ENOMEM, ENOSPC + do_disk_error_action("reconfig", data->config, + saved_errno); + } else { + logging_suspended = 0; + check_log_file_size(data); + } + } + + /* At this point we will start working on items that are + * related to the amount of space on the partition. */ + + // space left + if (oconf->space_left != nconf->space_left) { + oconf->space_left = nconf->space_left; + need_space_check = 1; + } + + // space left action + if (oconf->space_left_action != nconf->space_left_action) { + oconf->space_left_action = nconf->space_left_action; + need_space_check = 1; + } + + // space left exe + if (oconf->space_left_exe || nconf->space_left_exe) { + if (nconf->space_left_exe == NULL) + ; /* do nothing if new one is blank */ + else if (oconf->space_left_exe == NULL && nconf->space_left_exe) + need_space_check = 1; + else if (strcmp(oconf->space_left_exe, nconf->space_left_exe)) + need_space_check = 1; + free((char *)oconf->space_left_exe); + oconf->space_left_exe = nconf->space_left_exe; + } + + // admin space left + if (oconf->admin_space_left != nconf->admin_space_left) { + oconf->admin_space_left = nconf->admin_space_left; + need_space_check = 1; + } + + // admin space action + if (oconf->admin_space_left_action != nconf->admin_space_left_action) { + oconf->admin_space_left_action = nconf->admin_space_left_action; + need_space_check = 1; + } + + // admin space left exe + if (oconf->admin_space_left_exe || nconf->admin_space_left_exe) { + if (nconf->admin_space_left_exe == NULL) + ; /* do nothing if new one is blank */ + else if (oconf->admin_space_left_exe == NULL && + nconf->admin_space_left_exe) + need_space_check = 1; + else if (strcmp(oconf->admin_space_left_exe, + nconf->admin_space_left_exe)) + need_space_check = 1; + free((char *)oconf->admin_space_left_exe); + oconf->admin_space_left_exe = nconf->admin_space_left_exe; + } + // disk full action + if (oconf->disk_full_action != nconf->disk_full_action) { + oconf->disk_full_action = nconf->disk_full_action; + need_space_check = 1; + } + + // disk full exe + if (oconf->disk_full_exe || nconf->disk_full_exe) { + if (nconf->disk_full_exe == NULL) + ; /* do nothing if new one is blank */ + else if (oconf->disk_full_exe == NULL && nconf->disk_full_exe) + need_space_check = 1; + else if (strcmp(oconf->disk_full_exe, nconf->disk_full_exe)) + need_space_check = 1; + free((char *)oconf->disk_full_exe); + oconf->disk_full_exe = nconf->disk_full_exe; + } + + if (need_space_check) { + /* note save suspended flag, then do space_left. If suspended + * is still 0, then copy saved suspended back. This avoids + * having to call check_log_file_size to restore it. */ + int saved_suspend = logging_suspended; + + fs_space_warning = 0; + fs_admin_space_warning = 0; + fs_space_left = 1; + logging_suspended = 0; + check_excess_logs(data); + check_space_left(data->log_fd, data); + if (logging_suspended == 0) + logging_suspended = saved_suspend; + } + + // Next document the results + srand(time(NULL)); + seq_num = rand()%10000; + if (gettimeofday(&tv, NULL) == 0) { + snprintf(date, sizeof(date), "audit(%lu.%03u:%u)", tv.tv_sec, + (unsigned)(tv.tv_usec/1000), seq_num); + } else { + snprintf(date, sizeof(date), + "audit(%lu.%03u:%u)", (unsigned long)time(NULL), + 0, seq_num); + } + + data->head->reply.len = snprintf(txt, sizeof(txt), + "%s config changed, auid=%u pid=%d subj=%s res=success", date, + uid, pid, ctx ); + audit_msg(LOG_NOTICE, "%s", txt); + data->head->reply.type = AUDIT_DAEMON_CONFIG; + data->head->reply.message = strdup(txt); + if (!data->head->reply.message) { + data->head->reply.len = 0; + audit_msg(LOG_ERR, "Cannot allocate config message"); + // FIXME: Should call some error handler + } + free((char *)ctx); +} + |