/
githubmirror
/
audit-userspace
Обзор
Документация
Войти
/
githubmirror
/
audit-userspace
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v4.0
src/auditd-event.c
1 668 строк
43 KB
Steve Grubb
Silence a couple gcc warnings
29 ноя 2023, 23:50
29 ноя 2023, 23:50
2807169
Код
Авторство
О чём код?
/* auditd-event.c -- * Copyright 2004-08,2011,2013,2015-16,2018,2021 Red Hat Inc. * 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 <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 <time.h> #include <sys/time.h> #include <sys/vfs.h> #include <limits.h> /* POSIX_HOST_NAME_MAX */ #include <ctype.h> /* toupper */ #include <libgen.h> /* dirname */ #include "auditd-event.h" #include "auditd-dispatch.h" #include "auditd-listen.h" #include "libaudit.h" #include "private.h" #include "auparse.h" #include "auparse-idata.h" /* This is defined in auditd.c */ extern volatile int stop; /* Local function prototypes */ static void send_ack(const struct auditd_event *e, int ack_type, const char *msg); static void write_to_log(const struct auditd_event *e); static void check_log_file_size(void); static void check_space_left(void); static void do_space_left_action(int admin); static void do_disk_full_action(void); static void do_disk_error_action(const char *func, int err); static void fix_disk_permissions(void); static void check_excess_logs(void); static void rotate_logs_now(void); static void rotate_logs(unsigned int num_logs, unsigned int keep_logs); static void shift_logs(void); static int open_audit_log(void); static void change_runlevel(const char *level); static void safe_exec(const char *exe); static void reconfigure(struct auditd_event *e); static void init_flush_thread(void); /* Local Data */ static struct daemon_conf *config; static volatile int log_fd; static FILE *log_file = NULL; 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 unsigned int known_logs = 0; static const char *SINGLE = "1"; static const char *HALT = "0"; static char *format_buf = NULL; static off_t log_size = 0; static pthread_t flush_thread; static pthread_mutex_t flush_lock; static pthread_cond_t do_flush; static volatile int flush; static auparse_state_t *au = NULL; /* Local definitions */ #define FORMAT_BUF_LEN (MAX_AUDIT_MESSAGE_LENGTH + _POSIX_HOST_NAME_MAX) #define MIN_SPACE_LEFT 24 static inline int from_network(const struct auditd_event *e) { if (e && e->ack_func) return 1; return 0; } int dispatch_network_events(void) { return config->distribute_network_events; } void write_logging_state(FILE *f) { fprintf(f, "writing to logs = %s\n", config->write_logs ? "yes" : "no"); if (config->daemonize == D_BACKGROUND && config->write_logs) { int rc; struct statfs buf; fprintf(f, "current log size = %llu KB\n", (long long unsigned)log_size/1024); fprintf(f, "max log size = %lu KB\n", config->max_log_size * (MEGABYTE/1024)); fprintf(f,"logs detected last rotate/shift = %u\n", known_logs); fprintf(f, "space left on partition = %s\n", fs_space_left ? "yes" : "no"); rc = fstatfs(log_fd, &buf); if (rc == 0) { fprintf(f, "Logging partition free space %llu MB\n", (long long unsigned) (buf.f_bavail * buf.f_bsize)/MEGABYTE); fprintf(f, "space_left setting %lu MB\n", config->space_left); fprintf(f, "admin_space_left setting %lu MB\n", config->admin_space_left); } fprintf(f, "logging suspended = %s\n", logging_suspended ? "yes" : "no"); fprintf(f, "file system space action performed = %s\n", fs_space_warning ? "yes" : "no"); fprintf(f, "admin space action performed = %s\n", fs_admin_space_warning ? "yes" : "no"); fprintf(f, "disk error detected = %s\n", disk_err_warning ? "yes" : "no"); } } void shutdown_events(void) { // We are no longer processing events, sync the disk and close up. pthread_cancel(flush_thread); free((void *)format_buf); auparse_destroy_ext(au, AUPARSE_DESTROY_ALL); if (log_fd >= 0) fsync(log_fd); if (log_file) fclose(log_file); } int init_event(struct daemon_conf *conf) { /* Store the netlink descriptor and config info away */ config = conf; log_fd = -1; /* Now open the log */ if (config->daemonize == D_BACKGROUND) { fix_disk_permissions(); if (open_audit_log()) return 1; setup_percentages(config, log_fd); } else { log_fd = 1; // stdout log_file = fdopen(log_fd, "a"); if (log_file == NULL) { audit_msg(LOG_ERR, "Error setting up stdout descriptor (%s)", strerror(errno)); return 1; } /* Set it to line buffering */ setlinebuf(log_file); } if (config->daemonize == D_BACKGROUND) { check_log_file_size(); check_excess_logs(); check_space_left(); } format_buf = (char *)malloc(FORMAT_BUF_LEN); if (format_buf == NULL) { audit_msg(LOG_ERR, "No memory for formatting, exiting"); if (log_file) fclose(log_file); log_file = NULL; return 1; } init_flush_thread(); return 0; } /* This tells the OS that pending writes need to get going. * Its only used when flush == incremental_async. */ static void *flush_thread_main(void *arg) { sigset_t sigs; /* This is a worker thread. Don't handle signals. */ sigemptyset(&sigs); sigaddset(&sigs, SIGTERM); sigaddset(&sigs, SIGHUP); sigaddset(&sigs, SIGUSR1); sigaddset(&sigs, SIGUSR2); sigaddset(&sigs, SIGCHLD); sigaddset(&sigs, SIGCONT); pthread_sigmask(SIG_SETMASK, &sigs, NULL); while (!stop) { pthread_mutex_lock(&flush_lock); // In the event that the logging thread requests another // flush before the first completes, this simply turns // into a loop of fsyncs. while (flush == 0) { pthread_cond_wait(&do_flush, &flush_lock); if (stop) { pthread_mutex_unlock(&flush_lock); return NULL; } } flush = 0; pthread_mutex_unlock(&flush_lock); if (log_fd >= 0) fsync(log_fd); } return NULL; } /* We setup the flush thread no matter what. This is incase a reconfig * changes from non incremental to incremental or vice versa. */ static void init_flush_thread(void) { pthread_mutex_init(&flush_lock, NULL); pthread_cond_init(&do_flush, NULL); flush = 0; pthread_create(&flush_thread, NULL, flush_thread_main, NULL); pthread_detach(flush_thread); } static void replace_event_msg(struct auditd_event *e, const char *buf) { if (buf) { size_t len = strlen(buf); if (len < MAX_AUDIT_MESSAGE_LENGTH - 1) e->reply.message = strdup(buf); else { // If too big, we must truncate the event due to API e->reply.message = strndup(buf, MAX_AUDIT_MESSAGE_LENGTH-1); len = MAX_AUDIT_MESSAGE_LENGTH; } // For network originating events, len should be used if (!from_network(e)) // V1 protocol msg size e->reply.msg.nlh.nlmsg_len = e->reply.len; e->reply.len = len; // V2 protocol msg size } } /* * This function will take an audit structure and return a * text buffer that's formatted for writing to disk. If there * is an error the return value is NULL. */ static const char *format_raw(const struct audit_reply *rep) { char *ptr; if (rep == NULL) { if (config->node_name_format != N_NONE) snprintf(format_buf, FORMAT_BUF_LEN - 32, "node=%s type=DAEMON_ERR op=format-raw msg=NULL res=failed", config->node_name); else snprintf(format_buf, MAX_AUDIT_MESSAGE_LENGTH, "type=DAEMON_ERR op=format-raw msg=NULL res=failed"); } 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 = "lost"; len = 4; } 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, FORMAT_BUF_LEN - 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 int sep_done = 0; static int add_separator(unsigned int len_left) { if (sep_done == 0) { format_buf[FORMAT_BUF_LEN - len_left] = AUDIT_INTERP_SEPARATOR; sep_done++; return 1; } sep_done++; return 0; } // returns length used, 0 on error #define NAME_SIZE 64 static int add_simple_field(auparse_state_t *au, size_t len_left, int encode) { const char *value, *nptr; char *enc = NULL; char *ptr, field_name[NAME_SIZE]; size_t nlen, vlen, tlen; unsigned int i; int num; // prepare field name i = 0; nptr = auparse_get_field_name(au); while (*nptr && i < (NAME_SIZE - 1)) { field_name[i] = toupper(*nptr); i++; nptr++; } field_name[i] = 0; nlen = i; // get the translated value value = auparse_interpret_field(au); if (value == NULL) value = "?"; vlen = strlen(value); if (encode) { enc = audit_encode_nv_string(field_name, value, vlen); if (enc == NULL) return 0; tlen = 1 + strlen(enc) + 1; } else // calculate length to use tlen = 1 + nlen + 1 + vlen + 1; // If no room, do not truncate - just do nothing if (tlen >= len_left) { free(enc); return 0; } // Setup pointer ptr = &format_buf[FORMAT_BUF_LEN - len_left]; if (sep_done > 1) { *ptr = ' '; ptr++; num = 1; } else num = 0; // Add the field if (encode) { num += snprintf(ptr, tlen, "%s", enc); free(enc); } else num += snprintf(ptr, tlen, "%s=%s", field_name, value); return num; } /* * This function will take an audit structure and return a * text buffer that's formatted and enriched. If there is an * error the return value is NULL. */ static const char *format_enrich(const struct audit_reply *rep) { if (rep == NULL) { if (config->node_name_format != N_NONE) snprintf(format_buf, FORMAT_BUF_LEN - 32, "node=%s type=DAEMON_ERR op=format-enriched msg=NULL res=failed", config->node_name); else snprintf(format_buf, MAX_AUDIT_MESSAGE_LENGTH, "type=DAEMON_ERR op=format-enriched msg=NULL res=failed"); } else { int rc, rtype; size_t mlen, len; char *message; // Do raw format to get event started format_raw(rep); // How much room is left? mlen = strlen(format_buf); len = FORMAT_BUF_LEN - mlen; if (len <= MIN_SPACE_LEFT) return format_buf; // create copy to parse up format_buf[mlen] = 0x0A; format_buf[mlen+1] = 0; message = strdup(format_buf); format_buf[mlen] = 0; // init auparse if (au == NULL) { au = auparse_init(AUSOURCE_BUFFER, message); if (au == NULL) { free(message); return format_buf; } auparse_set_escape_mode(au, AUPARSE_ESC_RAW); auparse_set_eoe_timeout(config->end_of_event_timeout); } else auparse_new_buffer(au, message, mlen+1); sep_done = 0; // Loop over all fields while possible to add field rc = auparse_first_record(au); rtype = auparse_get_type(au); switch (rtype) { // Flush before adding to pickup new associations case AUDIT_ADD_USER: case AUDIT_ADD_GROUP: _auparse_flush_caches(); break; default: break; } while (rc > 0 && len > MIN_SPACE_LEFT) { // See what kind of field we have size_t vlen; int type = auparse_get_field_type(au); switch (type) { case AUPARSE_TYPE_UID: case AUPARSE_TYPE_GID: if (add_separator(len)) len--; vlen = add_simple_field(au, len, 1); len -= vlen; break; case AUPARSE_TYPE_SYSCALL: case AUPARSE_TYPE_ARCH: case AUPARSE_TYPE_SOCKADDR: if (add_separator(len)) len--; vlen = add_simple_field(au, len, 0); len -= vlen; break; default: break; } rc = auparse_next_field(au); } switch(rtype) { // Flush after modification to remove stale entries case AUDIT_USER_MGMT: case AUDIT_DEL_USER: case AUDIT_DEL_GROUP: case AUDIT_GRP_MGMT: _auparse_flush_caches(); break; default: break; } free(message); } return format_buf; } void format_event(struct auditd_event *e) { const char *buf; switch (config->log_format) { case LF_RAW: buf = format_raw(&e->reply); break; case LF_ENRICHED: buf = format_enrich(&e->reply); break; default: buf = NULL; break; } replace_event_msg(e, buf); } /* This function free's all memory associated with events */ void cleanup_event(struct auditd_event *e) { // Over in send_audit_event we sometimes have message pointing // into the middle of the reply allocation. Check for it. if (e->reply.message != e->reply.msg.data) free((void *)e->reply.message); free(e); } /* This function takes a reconfig event and sends it to the handler */ void enqueue_event(struct auditd_event *e) { e->ack_func = NULL; e->ack_data = NULL; e->sequence_id = 0; handle_event(e); cleanup_event(e); } /* This function allocates memory and fills the event fields with passed arguments. Caller must free memory. */ struct auditd_event *create_event(const char *msg, ack_func_type ack_func, void *ack_data, uint32_t sequence_id) { struct auditd_event *e; e = (struct auditd_event *)calloc(1, sizeof (*e)); if (e == NULL) { audit_msg(LOG_ERR, "Cannot allocate audit reply"); return NULL; } e->ack_func = ack_func; e->ack_data = ack_data; e->sequence_id = sequence_id; /* Network originating events need things adjusted to mimic netlink. */ if (from_network(e)) replace_event_msg(e, msg); return e; } /* This function takes the event and handles it. */ static unsigned int count = 0L; void handle_event(struct auditd_event *e) { if (e->reply.type == AUDIT_DAEMON_RECONFIG && e->ack_func == NULL) { reconfigure(e); if (config->write_logs == 0 && config->daemonize == D_BACKGROUND) return; format_event(e); } else if (e->reply.type == AUDIT_DAEMON_ROTATE) { rotate_logs_now(); if (config->write_logs == 0 && config->daemonize == D_BACKGROUND) return; } if (!logging_suspended && (config->write_logs || config->daemonize == D_FOREGROUND)) { write_to_log(e); /* See if we need to flush to disk manually */ if (config->flush == FT_INCREMENTAL || config->flush == FT_INCREMENTAL_ASYNC) { count++; if ((count % config->freq) == 0) { int rc; errno = 0; do { rc = fflush_unlocked(log_file); } while (rc < 0 && errno == EINTR); if (errno) { if (errno == ENOSPC && fs_space_left == 1) { fs_space_left = 0; do_disk_full_action(); } else //EIO is only likely failure mode do_disk_error_action("flush", errno); } if (config->daemonize == D_BACKGROUND) { if (config->flush == FT_INCREMENTAL) { /* EIO is only likely failure */ if (log_fd >= 0 && fsync(log_fd) != 0) { do_disk_error_action( "fsync", errno); } } else { pthread_mutex_lock(&flush_lock); flush = 1; pthread_cond_signal(&do_flush); pthread_mutex_unlock( &flush_lock); } } } } } else if (!config->write_logs && config->daemonize == D_BACKGROUND) send_ack(e, AUDIT_RMW_TYPE_ACK, ""); else if (logging_suspended) send_ack(e,AUDIT_RMW_TYPE_DISKERROR,"remote logging suspended"); } static void send_ack(const struct auditd_event *e, int ack_type, const char *msg) { if (from_network(e)) { unsigned char header[AUDIT_RMW_HEADER_SIZE]; AUDIT_RMW_PACK_HEADER(header, 0, ack_type, strlen(msg), e->sequence_id); e->ack_func(e->ack_data, header, msg); } } void resume_logging(void) { audit_msg(LOG_NOTICE, "Audit daemon is attempting to resume logging."); logging_suspended = 0; fs_space_left = 1; // User space action scripts cause fd to close // Need to reopen here to recreate the file if the // script deleted or moved it. if (log_file == NULL) { fix_disk_permissions(); if (open_audit_log()) { int saved_errno = errno; audit_msg(LOG_WARNING, "Could not reopen a log after resume logging"); logging_suspended = 1; do_disk_error_action("resume", saved_errno); } else check_log_file_size(); audit_msg(LOG_NOTICE, "Audit daemon resumed logging."); } disk_err_warning = 0; fs_space_warning = 0; fs_admin_space_warning = 0; } /* This function writes the given buf to the current log file */ static void write_to_log(const struct auditd_event *e) { int rc; int ack_type = AUDIT_RMW_TYPE_ACK; const char *msg = ""; /* write it to disk */ rc = fprintf(log_file, "%s\n", e->reply.message); /* error? Handle it */ if (rc < 0) { if (errno == ENOSPC) { ack_type = AUDIT_RMW_TYPE_DISKFULL; msg = "disk full"; send_ack(e, ack_type, msg); if (fs_space_left == 1) { fs_space_left = 0; do_disk_full_action(); } } else { int saved_errno = errno; ack_type = AUDIT_RMW_TYPE_DISKERROR; msg = "disk write error"; send_ack(e, ack_type, msg); do_disk_error_action("write", 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(); // Keep loose tabs on the free space if ((log_size % 8) < 3) check_space_left(); } if (fs_space_warning) ack_type = AUDIT_RMW_TYPE_DISKLOW; send_ack(e, ack_type, msg); disk_err_warning = 0; } } static void check_log_file_size(void) { /* did we cross the size limit? */ off_t sz = log_size / MEGABYTE; if (config->write_logs == 0) return; 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."); // We need to close the file so that manual // intervention can move or delete the file. // We don't want to keep logging to a deleted // file. if (log_file) fclose(log_file); log_file = NULL; log_fd = -1; logging_suspended = 1; break; case SZ_ROTATE: if (config->num_logs > 1) { audit_msg(LOG_NOTICE, "Audit daemon rotating log files"); rotate_logs(0, 0); } break; case SZ_KEEP_LOGS: audit_msg(LOG_NOTICE, "Audit daemon rotating log files with keep option"); shift_logs(); 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(void) { int rc; struct statfs buf; if (log_fd < 0) return; rc = fstatfs(log_fd, &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(); } 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(0); // Allow unlimited rotation if (config->space_left_action != FA_ROTATE) 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(1); // Allow unlimited rotation if (config->admin_space_left_action != FA_ROTATE) 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(int admin) { int action; 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(0, 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: // Close the logging file in case the script zips or // moves the file. We'll reopen in sigusr2 handler if (log_file) fclose(log_file); log_file = NULL; log_fd = -1; logging_suspended = 1; 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."); // We need to close the file so that manual // intervention can move or delete the file. We // don't want to keep logging to a deleted file. if (log_file) fclose(log_file); log_file = NULL; log_fd = -1; logging_suspended = 1; break; case FA_SINGLE: audit_msg(LOG_ALERT, "The audit daemon is now changing the system to single user mode and exiting due to low disk space"); change_runlevel(SINGLE); stop = 1; break; case FA_HALT: audit_msg(LOG_ALERT, "The audit daemon is now halting the system and exiting due to low disk space"); change_runlevel(HALT); stop = 1; 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(void) { 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(0, 0); } break; case FA_EXEC: // Close the logging file in case the script zips or // moves the file. We'll reopen in sigusr2 handler if (log_file) fclose(log_file); log_file = NULL; log_fd = -1; logging_suspended = 1; 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."); // We need to close the file so that manual // intervention can move or delete the file. We // don't want to keep logging to a deleted file. if (log_file) fclose(log_file); log_file = NULL; log_fd = -1; logging_suspended = 1; break; case FA_SINGLE: audit_msg(LOG_ALERT, "The audit daemon is now changing the system to single user mode and exiting due to no space left on logging partition"); change_runlevel(SINGLE); stop = 1; break; case FA_HALT: audit_msg(LOG_ALERT, "The audit daemon is now halting the system and exiting due to no space left on logging partition"); change_runlevel(HALT); stop = 1; break; default: audit_msg(LOG_ALERT, "Unknown disk full action requested"); break; } } static void do_disk_error_action(const char *func, 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: // Close the logging file in case the script zips or // moves the file. We'll reopen in sigusr2 handler if (log_file) fclose(log_file); log_file = NULL; log_fd = -1; logging_suspended = 1; 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"); // We need to close the file so that manual // intervention can move or delete the file. We // don't want to keep logging to a deleted file. if (log_file) fclose(log_file); log_file = NULL; log_fd = -1; logging_suspended = 1; break; case FA_SINGLE: audit_msg(LOG_ALERT, "The audit daemon is now changing the system to single user mode and exiting due to previously mentioned write error"); change_runlevel(SINGLE); stop = 1; break; case FA_HALT: audit_msg(LOG_ALERT, "The audit daemon is now halting the system and exiting due to previously mentioned write error."); change_runlevel(HALT); stop = 1; break; default: audit_msg(LOG_ALERT, "Unknown disk error action requested"); break; } } static void rotate_logs_now(void) { if (config->max_log_size_action == SZ_KEEP_LOGS) shift_logs(); else rotate_logs(0, 0); } /* Check for and remove excess logs so that we don't run out of room */ static void check_excess_logs(void) { 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 (config->max_log_size_action != SZ_ROTATE || config->num_logs < 2) return; len = strlen(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 = config->num_logs; rc = 0; while (rc == 0) { snprintf(name, len, "%s.%u", 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 fix_disk_permissions(void) { char *path, *dir; unsigned int i, len; if (config == NULL || config->log_file == NULL) return; len = strlen(config->log_file) + 16; path = malloc(len); if (path == NULL) return; // Start with the directory strcpy(path, config->log_file); dir = dirname(path); if (chmod(dir,config->log_group ? S_IRWXU|S_IRGRP|S_IXGRP: S_IRWXU) < 0) audit_msg(LOG_WARNING, "Couldn't change access mode of " "%s (%s)", dir, strerror(errno)); if (chown(dir, 0, config->log_group ? config->log_group : 0) < 0) audit_msg(LOG_WARNING, "Couldn't change ownership of " "%s (%s)", dir, strerror(errno)); // Now, for each file... for (i = 1; i < config->num_logs; i++) { int rc; snprintf(path, len, "%s.%u", config->log_file, i); rc = chmod(path, config->log_group ? S_IRUSR|S_IRGRP : S_IRUSR); if (rc && errno == ENOENT) break; } // Now the current file chmod(config->log_file, config->log_group ? S_IWUSR|S_IRUSR|S_IRGRP : S_IWUSR|S_IRUSR); free(path); } static void rotate_logs(unsigned int num_logs, unsigned int keep_logs) { int rc, i; unsigned int len; char *oldname, *newname; /* Check that log rotation is enabled in the configuration file. There * is no need to check for max_log_size_action == SZ_ROTATE because * this could be invoked externally by receiving a USR1 signal, * independently on the action parameter. */ if (config->num_logs < 2 && !keep_logs){ audit_msg(LOG_NOTICE, "Log rotation disabled (num_logs < 2), skipping"); 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 (log_fd >= 0) { if (fchmod(log_fd, config->log_group ? S_IRUSR|S_IRGRP : S_IRUSR) < 0){ audit_msg(LOG_WARNING, "Couldn't change permissions while " "rotating log file (%s)", strerror(errno)); } if (fchown(log_fd, 0, config->log_group) < 0) { audit_msg(LOG_WARNING, "Couldn't change ownership while " "rotating log file (%s)", strerror(errno)); } } if (log_file) { log_fd = -1; fclose(log_file); log_file = NULL; } /* Rotate */ len = strlen(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 = config->num_logs; /* Handle this case first since it will not enter the for loop */ if (num_logs == 2) snprintf(oldname, len, "%s.1", config->log_file); known_logs = 0; for (i=(int)num_logs - 1; i>1; i--) { snprintf(oldname, len, "%s.%d", config->log_file, i-1); snprintf(newname, len, "%s.%d", 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(); } else do_disk_error_action("rotate", saved_errno); } else if (rc == 0 && known_logs == 0) known_logs = i + 1; } free(newname); /* At this point, oldname should point to lowest number - use it */ newname = oldname; rc = rename(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)", config->log_file, newname, strerror(errno)); if (saved_errno == ENOSPC && fs_space_left == 1) { fs_space_left = 0; do_disk_full_action(); } else do_disk_error_action("rotate2", 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(config->log_file, config->log_group ? S_IWUSR|S_IRUSR|S_IRGRP : S_IWUSR|S_IRUSR); } free(newname); /* open new audit file */ if (open_audit_log()) { int saved_errno = errno; audit_msg(LOG_CRIT, "Could not reopen a log after rotating."); logging_suspended = 1; do_disk_error_action("reopen", saved_errno); } } static unsigned int last_log = 1; static void shift_logs(void) { // 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(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.%u", config->log_file, num_logs); if (access(name, R_OK) != 0) break; num_logs++; } known_logs = 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.%u", 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(num_logs+1, 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(void) { int flags, lfd; if (config->write_logs == 0) return 0; flags = O_WRONLY|O_APPEND|O_NOFOLLOW|O_CLOEXEC; if (config->flush == FT_DATA) flags |= O_DSYNC; else if (config->flush == FT_SYNC) flags |= O_SYNC; // Likely errors for open: Almost anything // Likely errors on rotate: ENFILE, ENOMEM, ENOSPC retry: lfd = open(config->log_file, flags); if (lfd < 0) { if (errno == ENOENT) { lfd = create_log_file(config->log_file); if (lfd < 0) { audit_msg(LOG_CRIT, "Couldn't create log file %s (%s)", config->log_file, strerror(errno)); return 1; } close(lfd); lfd = open(config->log_file, flags); log_size = 0; } else if (errno == ENFILE) { // All system descriptors used, try again... goto retry; } if (lfd < 0) { audit_msg(LOG_CRIT, "Couldn't open log file %s (%s)", 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 (fchmod(lfd, 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, config->log_group) < 0) { audit_msg(LOG_ERR, "Couldn't change ownership of log file (%s)", strerror(errno)); close(lfd); return 1; } log_fd = lfd; log_file = fdopen(lfd, "a"); if (log_file == NULL) { audit_msg(LOG_CRIT, "Error setting up log descriptor (%s)", strerror(errno)); close(lfd); return 1; } /* Set it to line buffering */ setlinebuf(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); } static void reconfigure(struct auditd_event *e) { struct daemon_conf *nconf = e->reply.conf; struct daemon_conf *oconf = 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; // number of logs oconf->num_logs = nconf->num_logs; // flush freq oconf->freq = nconf->freq; // priority boost if (oconf->priority_boost != nconf->priority_boost) { oconf->priority_boost = nconf->priority_boost; errno = 0; if (nice(-oconf->priority_boost)) ; /* Intentionally blank, we have to check errno */ if (errno) audit_msg(LOG_WARNING, "Cannot change priority in " "reconfigure (%s)", strerror(errno)); } // log format oconf->log_format = nconf->log_format; // Only update this if we are in background mode since // foreground mode writes to stderr. if ((oconf->write_logs != nconf->write_logs) && (oconf->daemonize == D_BACKGROUND)) { oconf->write_logs = nconf->write_logs; need_reopen = 1; } // log_group if (oconf->log_group != nconf->log_group) { oconf->log_group = nconf->log_group; need_reopen = 1; } // 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; } // network listener auditd_tcp_listen_reconfigure(nconf, oconf); // distribute network events oconf->distribute_network_events = nconf->distribute_network_events; // Dispatcher items oconf->q_depth = nconf->q_depth; oconf->overflow_action = nconf->overflow_action; oconf->max_restarts = nconf->max_restarts; if (oconf->plugin_dir != nconf->plugin_dir || (oconf->plugin_dir && nconf->plugin_dir && strcmp(oconf->plugin_dir, nconf->plugin_dir) != 0)) { free(oconf->plugin_dir); oconf->plugin_dir = nconf->plugin_dir; } /* 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(); } // 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) { if (log_file) fclose(log_file); log_file = NULL; fix_disk_permissions(); if (open_audit_log()) { int saved_errno = errno; audit_msg(LOG_ERR, "Could not reopen a log after reconfigure"); logging_suspended = 1; // Likely errors: ENOMEM, ENOSPC do_disk_error_action("reconfig", saved_errno); } else { logging_suspended = 0; check_log_file_size(); } } /* 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 percent if (oconf->space_left_percent != nconf->space_left_percent) { oconf->space_left_percent = nconf->space_left_percent; 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 left percent if (oconf->admin_space_left_percent != nconf->admin_space_left_percent){ oconf->admin_space_left_percent = nconf->admin_space_left_percent; 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; setup_percentages(oconf, log_fd); fs_space_warning = 0; fs_admin_space_warning = 0; fs_space_left = 1; logging_suspended = 0; check_excess_logs(); check_space_left(); if (logging_suspended == 0) logging_suspended = saved_suspend; } reconfigure_dispatcher(oconf); // Next document the results srand(time(NULL)); seq_num = rand()%10000; if (gettimeofday(&tv, NULL) == 0) { snprintf(date, sizeof(date), "audit(%lld.%03u:%u)", (long long int)tv.tv_sec, (unsigned)(tv.tv_usec/1000), seq_num); } else { snprintf(date, sizeof(date), "audit(%lld.%03d:%u)", (long long int)time(NULL), 0, seq_num); } e->reply.type = AUDIT_DAEMON_CONFIG; e->reply.len = snprintf(e->reply.msg.data, MAX_AUDIT_MESSAGE_LENGTH-2, "%s: op=reconfigure state=changed auid=%u pid=%d subj=%s res=success", date, uid, pid, ctx ); e->reply.message = e->reply.msg.data; free((char *)ctx); }