Logo Search packages:      
Sourcecode: audit version File versions  Download package

auditd-event.c
/* auditd-event.c -- 
 * Copyright 2004-08 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;

00046 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(int lfd, struct auditd_consumer_data *data);
static void check_space_left(int lfd, struct daemon_conf *config);
static void do_space_left_action(struct daemon_conf *config, int admin);
static void do_disk_full_action(struct daemon_conf *config);
static void do_disk_error_action(const char *func, struct daemon_conf *config);
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 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;


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.log_fd, &consumer_data);
            check_space_left(consumer_data.log_fd, config);
      }
      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;
      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 ) {
                        free(rep);
                        return;
                  }
                  break;
            default:
                  audit_msg(LOG_ERR, 
                          "Illegal log format detected %d", 
                          consumer_data.config->log_format);
                  free(rep);
                  return;
            }

            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;
      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->config);
                                } else
                                   //EIO is only likely failure mode
                                   do_disk_error_action("flush", 
                                    data->config);
                        }

                        /* EIO is only likely failure mode */
                        if ((data->config->daemonize == D_BACKGROUND)&& 
                                    (fsync(data->log_fd) != 0)) {
                             do_disk_error_action("fsync",
                              data->config);
                        }
                  }
            }
      }
}

/* 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) {
            int saved_errno = errno;
            audit_msg(LOG_ERR, "Record was not written to disk (%s)", 
                  strerror(errno));
            if (saved_errno == ENOSPC && fs_space_left == 1) {
                  fs_space_left = 0;
                  do_disk_full_action(config);
                  ack_type = AUDIT_RMW_TYPE_DISKFULL;
                  msg = "disk full";
            } else  {
                  do_disk_error_action("write", config);
                  ack_type = AUDIT_RMW_TYPE_DISKERROR;
                  msg = "disk write error";
            }
            
      } 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.
                  check_log_file_size(data->log_fd, data);
                  check_space_left(data->log_fd, config);
            }
      }

      if (data->head->ack_func) {
            unsigned char header[AUDIT_RMW_HEADER_SIZE];

            if (fs_space_warning)
                  ack_type = AUDIT_RMW_TYPE_DISKLOW;

            AUDIT_RMW_PACK_HEADER (header, 0, ack_type, strlen(msg), data->head->sequence_id);

            data->head->ack_func (data->head->ack_data, header, msg);
      }
}

static void check_log_file_size(int lfd, struct auditd_consumer_data *data)
{
      int rc;
      struct daemon_conf *config = data->config;
      struct stat st;

      /* get file size */
      rc = fstat(lfd, &st);
      if (rc == 0) {
            /* did we cross the size limit? */
            unsigned long sz = st.st_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;
                  }
            }
      } else 
            audit_msg(LOG_ERR, "Unable to fstat log file (%s)",
                  strerror(errno));
}

static void check_space_left(int lfd, struct daemon_conf *config)
{
      int rc;
      struct statfs buf;

        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(config);
            } 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(config, 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(config, 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 daemon_conf *config, 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_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 daemon_conf *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_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)
{
      char text[128];

      snprintf(text, sizeof(text), 
          "%s: Audit daemon detected an error writing an event to disk (%s)",
            func, strerror(errno));
      audit_msg(LOG_ALERT, "%s", text);

      switch (config->disk_error_action)
      {
            case FA_IGNORE:
            case FA_SYSLOG: /* Message is syslogged above */
                  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);
}
 
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->num_logs < 2)
            return;

      /* Close audit file */
      fchmod(data->log_fd, 
                  data->config->log_group ? S_IRUSR|S_IRGRP : S_IRUSR);
      fchown(data->log_fd, 0, data->config->log_group);
      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");
            return;
      }
      newname = (char *)malloc(len);
      if (newname == NULL) { /* Not fatal - just messy */
            audit_msg(LOG_ERR, "No memory rotating logs");
            free(oldname);
            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->config);
                  } else
                        do_disk_error_action("rotate", data->config);
            }
      }
      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->config);
            } else
                  do_disk_error_action("rotate2", data->config);

            /* 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)) {
            audit_msg(LOG_NOTICE, 
                  "Could not reopen a log after rotating.");
            logging_suspended = 1;
            do_disk_error_action("reopen", data->config);
      }
}

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);
            } 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;
            }
      }

      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;
            }
      }
      fchmod(lfd, data->config->log_group ? S_IRUSR|S_IWUSR|S_IRGRP : 
                                    S_IRUSR|S_IWUSR);
      fchown(lfd, 0, data->config->log_group);

      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;
      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 */
      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;

      pid = fork();
      if (pid < 0) {
            audit_msg(LOG_ALERT, 
                  "Audit daemon failed to fork doing safe_exec");
            return;
      }
      if (pid)    /* Parent */
            return;
      /* Child */
      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;

      // numlogs is next
      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;
            nice(-oconf->priority_boost);
      }

      // 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) {
                        audit_msg(LOG_NOTICE,
                              "Could not allocate dispatcher memory"
                              " in reconfigure");
                        // Likely errors: ENOMEM
                        do_disk_error_action("reconfig", data->config);
                  }
                  if(init_dispatcher(oconf)) {// dispatcher & qos is used
                        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);
                  }
            } 
            // 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) {
                        audit_msg(LOG_NOTICE,
                              "Could not allocate dispatcher memory"
                              " in reconfigure");
                        // Likely errors: ENOMEM
                        do_disk_error_action("reconfig", data->config);
                  }
                  if(init_dispatcher(oconf)) {// dispatcher & qos is used
                        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);
                  }
            }
            // they are the same app - just signal it
            else {
                  reconfigure_dispatcher();
                  free((char *)nconf->dispatcher);
                  nconf->dispatcher = NULL;
            }
      }

      /* Look at network things that do not need restarting */
      if (oconf->tcp_client_min_port != nconf->tcp_client_min_port ||
                oconf->tcp_client_max_port != nconf->tcp_client_max_port ||
                oconf->tcp_max_per_addr != nconf->tcp_max_per_addr) {
            oconf->tcp_client_min_port = nconf->tcp_client_min_port;
            oconf->tcp_client_max_port = nconf->tcp_client_max_port;
            oconf->tcp_max_per_addr = nconf->tcp_max_per_addr;
            auditd_set_ports(oconf->tcp_client_min_port,
                        oconf->tcp_client_max_port,
                        oconf->tcp_max_per_addr);
      }
      if (oconf->tcp_client_max_idle != nconf->tcp_client_max_idle) {
            oconf->tcp_client_max_idle = nconf->tcp_client_max_idle;
            periodic_reconfigure();
      }
      if (oconf->tcp_listen_port != nconf->tcp_listen_port ||
                  oconf->tcp_listen_queue != nconf->tcp_listen_queue) {
            oconf->tcp_listen_port = nconf->tcp_listen_port;
            oconf->tcp_listen_queue = nconf->tcp_listen_queue;
            // FIXME: need to restart the network stuff
      }
      
      /* 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->log_fd, 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)) {
                  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);
            } else
                  logging_suspended = 0;
            check_log_file_size(data->log_fd, 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_space_left(data->log_fd, oconf);
            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);
}


Generated by  Doxygen 1.6.0   Back to index