Logo Search packages:      
Sourcecode: audit version File versions

auditd.c

/* auditd.c -- 
 * Copyright 2004-06 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>
 *   Rickard E. (Rik) Faith <faith@redhat.com>
 */

#include "config.h"
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>

#include "libaudit.h"
#include "auditd-config.h"
#include "auditd-event.h"
#include "auditd-dispatch.h"
#include "private.h"

#define DEFAULT_BUF_SZ  384
#define DMSG_SIZE (DEFAULT_BUF_SZ + 48) 
#define SUCCESS 0
#define FAILURE 1

/* Global Data */
volatile int stop = 0;
volatile int hup = 0;
volatile int rot = 0;

/* Local data */
static int fd = -1;
static struct daemon_conf config;
static const char *pidfile = "/var/run/auditd.pid";
static int init_pipe[2];
static int do_fork = 1;

/* Local function prototypes */
static void close_down(void);
static void clean_exit(void);
static int get_reply(int fd, struct audit_reply *rep, int seq);


/*
 * Output a usage message
 */
static void usage(void)
{
      puts("Usage: auditd [ -f -l -n ]");
      exit(2);
}


/*
 * SIGTERM handler
 */ 
static void term_handler( int sig )
{
      stop = 1;
}

/*
 * Used with sigalrm to force exit
 */
static void thread_killer( int sig )
{
      exit(0);
}

/*
 * Used with sigalrm to force exit
 */
static void hup_handler( int sig )
{
      hup = 1;
}

/*
 * Used to force log rotation
 */
static void user1_handler( int sig )
{
      rot = 1;
}

/*
 * Used with email alerts to cleanup
 */
static void child_handler( int sig )
{
      while (waitpid(-1, NULL, WNOHANG) > 0)
            ; /* empty */
}

/*
 * This function is used to send start, stop, and abort messages 
 * to the audit log.
 */
static unsigned seq_num = 0;
int send_audit_event(int type, const char *str)
{
      struct auditd_reply_list *rep;
      struct timeval tv;
      
      if ((rep = malloc(sizeof(*rep))) == NULL) {
            audit_msg(LOG_ERR, "Cannot allocate audit reply");
            return 1;
      }

      rep->reply.type = type;
      rep->reply.message = (char *)malloc(DMSG_SIZE);
      if (rep->reply.message == NULL) {
            free(rep);
            audit_msg(LOG_ERR, "Cannot allocate local event message");
            return 1;
      }
      if (seq_num == 0) {
            srand(time(NULL));
            seq_num = rand()%10000;
      } else
            seq_num++;
      if (gettimeofday(&tv, NULL) == 0) {
            rep->reply.len = snprintf((char *)rep->reply.message,
                  DMSG_SIZE, "audit(%lu.%03u:%u) %s, auditd pid=%u", 
                  tv.tv_sec, (unsigned)(tv.tv_usec/1000), seq_num, str,
                  getpid());
      } else {
            rep->reply.len = snprintf((char *)rep->reply.message,
                  DMSG_SIZE, "audit(%lu.%03u:%u) %s, auditd pid=%u", 
                  (unsigned long)time(NULL), 0, seq_num, str, getpid());
      }
      if (rep->reply.len > DMSG_SIZE)
            rep->reply.len = DMSG_SIZE;

      enqueue_event(rep);
      pthread_yield(); /* Give the other thread a chance to log it. */

      return 0;
}

static int write_pid_file(void)
{
      int pidfd, len;
      char val[16];

      len = snprintf(val, sizeof(val), "%u\n", getpid());
      if (len < 0) {
            audit_msg(LOG_ERR, "Pid error (%s)", strerror(errno));
            pidfile = 0;
            return 1;
      }
      pidfd = open(pidfile, O_CREAT | O_TRUNC | O_NOFOLLOW | O_WRONLY, 0644);
      if (pidfd < 0) {
            audit_msg(LOG_ERR, "Unable to set pidfile (%s)",
                  strerror(errno));
            pidfile = 0;
            return 1;
      }
      (void)write(pidfd, val, (unsigned int)len);
      close(pidfd);
      return 0;
}

static void avoid_oom_killer(void)
{
      int oomfd;
      
      oomfd = open("/proc/self/oom_adj", O_NOFOLLOW | O_WRONLY);
      if (oomfd >= 0) {
            (void)write(oomfd, "-17", 3);
            close(oomfd);
            return;
      }
      // Old style kernel...perform another action here
}

/*
 * This function will take care of becoming a daemon. The parent
 * will wait until the child notifies it by writing into a special
 * pipe to signify that it successfully initialized. This prevents
 * a race in the init script where rules get loaded before the daemon
 * is ready and they wind up in syslog. The child returns 0 on success
 * and nonzero on failure. The parent returns nonzero on failure. On
 * success, the parent calls _exit with 0.
 */ 
static int become_daemon(void)
{
      int fd, rc;
      pid_t pid;
      int status;

      if (do_fork) {
            if (pipe(init_pipe) || 
                        fcntl(init_pipe[0], F_SETFD, FD_CLOEXEC) ||
                        fcntl(init_pipe[0], F_SETFD, FD_CLOEXEC))
                  return -1;
            pid = fork();
      } else
            pid = 0;

      switch (pid)
      {
            case 0:
                  /* No longer need this...   */
                  if (do_fork) 
                        close(init_pipe[0]);

                  /* Open stdin,out,err to /dev/null */
                  fd = open("/dev/null", O_RDWR);
                  if (fd < 0)
                        return -1;
                  if (dup2(fd, 0) < 0)
                        return -1;
                  if (dup2(fd, 1) < 0)
                        return -1;
                  if (dup2(fd, 2) < 0)
                        return -1;
                  close(fd);

                  /* Change to '/' */
                  chdir("/");

                  /* Change session */
                  if (setsid() < 0)
                        return -1;
                  break;
            case -1:
                  return -1;
                  break;
            default:
                  /* Wait for the child to say its done */
                  rc = read(init_pipe[0], &status, sizeof(status));
                  if (rc < 0)
                        return -1;

                  /* Success - die a happy death */
                  if (status == SUCCESS)
                        _exit(0);
                  else
                        return -1;
                  break;
      }

      return 0;
}

static void tell_parent(int status)
{
      int rc;

      if (config.daemonize != D_BACKGROUND || do_fork == 0)
            return;
      do {
            rc = write(init_pipe[1], &status, sizeof(status));
      } while (rc < 0 && errno == EINTR);
}

int main(int argc, char *argv[])
{
      struct sigaction sa;
      fd_set read_mask;
      struct auditd_reply_list *rep = NULL;
      struct rlimit limit;
      int hup_info_requested = 0, usr1_info_requested = 0;
      int i;

      /* Get params && set mode */
      config.daemonize = D_BACKGROUND;
      if (argc > 1) {
            for (i=1; i<argc; i++) {
                  if (strcmp(argv[i], "-f") == 0) 
                        config.daemonize = D_FOREGROUND;
                  else if (strcmp(argv[i], "-l") == 0)
                        set_allow_links(1);
                  else if (strcmp(argv[i], "-n") == 0)
                        do_fork = 0;
                  else
                        usage();
            }
      }

      // Make paramemters take effect
      if (config.daemonize == D_FOREGROUND)
            set_aumessage_mode(MSG_STDERR, DBG_YES);
      else {
            set_aumessage_mode(MSG_SYSLOG, DBG_NO);
            (void) umask( umask( 077 ) | 022 );
      }

#ifndef DEBUG
      /* Make sure we are root */
      if (getuid() != 0) {
            fprintf(stderr, "You must be root to run this program.\n");
            return 4;
      }
#endif

      /* Register sighandlers */
      sa.sa_flags = 0 ;
      sigemptyset( &sa.sa_mask ) ;
      /* Ignore all signals by default */
      sa.sa_handler = SIG_IGN;
      for (i=1; i<NSIG; i++)
            sigaction( i, &sa, NULL );
      /* Set handler for the ones we care about */
      sa.sa_handler = term_handler;
      sigaction( SIGTERM, &sa, NULL );
      sa.sa_handler = hup_handler;
      sigaction( SIGHUP, &sa, NULL );
      sa.sa_handler = user1_handler;
      sigaction( SIGUSR1, &sa, NULL );
      sa.sa_handler = SIG_IGN;
      sigaction( SIGUSR2, &sa, NULL );
      sa.sa_handler = child_handler;
      sigaction( SIGCHLD, &sa, NULL );

      atexit(clean_exit);

      /* Raise the rlimits in case we're being started from a shell
         * with restrictions. Not a fatal error.  */
      limit.rlim_cur = RLIM_INFINITY;
      limit.rlim_max = RLIM_INFINITY;
      setrlimit(RLIMIT_FSIZE, &limit);
      setrlimit(RLIMIT_CPU, &limit);

      /* Load the Configuration File */
      if (load_config(&config, TEST_AUDITD))
            return 6;

      if (config.priority_boost != 0) {
            errno = 0;
            (void) nice((int)-config.priority_boost);
            if (errno) {
                  audit_msg(LOG_ERR, "Cannot change priority (%s)", 
                              strerror(errno));
                  return 1;
            }
      } 
      
      /* Daemonize or stay in foreground for debugging */
      if (config.daemonize == D_BACKGROUND) {
            if (become_daemon() != 0) {
                  audit_msg(LOG_ERR, "Cannot daemonize (%s)",
                        strerror(errno));
                  tell_parent(FAILURE);
                  return 1;
            } 
            openlog("auditd", LOG_PID, LOG_DAEMON);
      }

      /* Init netlink */
      if ((fd = audit_open()) < 0) {
            audit_msg(LOG_ERR, "Cannot open netlink audit socket");
            tell_parent(FAILURE);
            return 1;
      }

      /* Init the event handler thread */
      write_pid_file();
      if (init_event(&config)) {
            if (pidfile)
                  unlink(pidfile);
            tell_parent(FAILURE);
            return 1;
      }

      if (init_dispatcher(&config)) {
            if (pidfile)
                  unlink(pidfile);
            tell_parent(FAILURE);
            return 1;
      }

      /* Write message to log that we are alive */
      {
            char start[DEFAULT_BUF_SZ];
            const char *fmt = audit_lookup_format((int)config.log_format);
            if (fmt == NULL)
                  fmt = "UNKNOWN";
//FIXME add SUBJ
            snprintf(start, sizeof(start), 
                "auditd start, ver=%s, format=%s, "
                "auid=%u pid=%d res=success",
                 VERSION, fmt, audit_getloginuid(), getpid());
            if (send_audit_event(AUDIT_DAEMON_START, start)) {
                  audit_msg(LOG_ERR, "Cannot send start message");
                  if (pidfile)
                        unlink(pidfile);
                  shutdown_dispatcher();
                  tell_parent(FAILURE);
                  return 1;
            }
      }

      /* Tell kernel not to kill us */
      avoid_oom_killer();

      /* let config manager init */
      init_config_manager();

      /* Tell the kernel we are alive */
      if (audit_set_pid(fd, getpid(), WAIT_YES) < 0) {
            char emsg[DEFAULT_BUF_SZ];
            snprintf(emsg, sizeof(emsg),
                  "auditd error halt, auid=%u pid=%d res=failed",
                  audit_getloginuid(), getpid());
            stop = 1;
//FIXME add subj
            send_audit_event(AUDIT_DAEMON_ABORT, emsg);
            audit_msg(LOG_ERR, "Unable to set audit pid, exiting");
            close_down();
            if (pidfile)
                  unlink(pidfile);
            shutdown_dispatcher();
            tell_parent(FAILURE);
            return 1;
      }

      /* Now tell parent that everything went OK */
      tell_parent(SUCCESS);

      /* Enable auditing just in case it was off */
      if (audit_set_enabled(fd, 1) < 0) {
            char emsg[DEFAULT_BUF_SZ];
            snprintf(emsg, sizeof(emsg),
                  "auditd error halt, auid=%u pid=%d res=failed",
                  audit_getloginuid(), getpid());
            stop = 1;
//FIXME add subj
            send_audit_event(AUDIT_DAEMON_ABORT, emsg);
            audit_msg(LOG_ERR, "Unable to enable auditing, exiting");
            close_down();
            if (pidfile)
                  unlink(pidfile);
            shutdown_dispatcher();
            return 1;
      }
      audit_msg(LOG_NOTICE, "Init complete, auditd %s listening for events",
            VERSION);

      /* Parent should be gone by now...   */
      if (do_fork)
            close(init_pipe[1]);

      for (;;) {
            struct timeval tv;
            int    retval;
       
            if (rep == NULL) { 
                  if ((rep = malloc(sizeof(*rep))) == NULL) {
                        char emsg[DEFAULT_BUF_SZ];
                        snprintf(emsg, sizeof(emsg), 
                      "auditd error halt, auid=%u pid=%d res=failed",
                            audit_getloginuid(), getpid());
                        stop = 1;
//FIXME add subj
                        send_audit_event(AUDIT_DAEMON_ABORT, emsg);
                        audit_msg(LOG_ERR, 
                              "Cannot allocate audit reply, exiting");
                        close_down();
                        if (pidfile)
                              unlink(pidfile);
                        shutdown_dispatcher();
                        return 1;
                  }
            }
            
            /* This will pause for 30 seconds */
            do {
                  tv.tv_sec = 30;
                  tv.tv_usec = 0;
                  FD_ZERO(&read_mask);
                  FD_SET(fd, &read_mask);
                  retval = select(fd+1, &read_mask, NULL, NULL, &tv);
            } while (retval == -1 && errno == EINTR && 
                        !stop && !hup && !rot);

            if (!stop && !hup && !rot && retval > 0) {
                  if (audit_get_reply(fd, &rep->reply, 
                              GET_REPLY_NONBLOCKING, 0) > 0) {
                        switch (rep->reply.type)
                        {     /* For now dont process these */
                              case NLMSG_NOOP:
                              case NLMSG_DONE:
                              case NLMSG_ERROR:
                              case AUDIT_GET: /* Or these */
                              case AUDIT_LIST:
                              case AUDIT_LIST_RULES:
                              case AUDIT_FIRST_DAEMON...AUDIT_LAST_DAEMON:
                                    break;
                              case AUDIT_SIGNAL_INFO:
                                    if (hup_info_requested) {
                                        audit_msg(LOG_DEBUG,
                        "HUP detected, starting config manager");
                                        if (start_config_manager(rep)) {
                                          send_audit_event(
                                          AUDIT_DAEMON_CONFIG, 
                        "auditd error getting hup info - no change,"
                        " sending auid=? pid=? subj=? res=failed");
                                        } else {
                                          free(rep);
                                          rep = NULL;
                                        }
                                        hup_info_requested = 0;
                                    } else if(usr1_info_requested){
                                          char usr1[MAX_AUDIT_MESSAGE_LENGTH];
                                          if (rep->reply.len == 24) {
                                                snprintf(usr1, 
                                                sizeof(usr1),
                              "auditd sending auid=? pid=? subj=?");
                                          } else {
                                                snprintf(usr1, 
                                                sizeof(usr1),
                              "auditd sending auid=%u pid=%d subj=%s",
                                    rep->reply.signal_info->uid, 
                                    rep->reply.signal_info->pid,
                                    rep->reply.signal_info->ctx);
                                          }
                                          send_audit_event(
                                          AUDIT_DAEMON_ROTATE, 
                                          usr1);
                                    }
                                    break;
                              default:
                                    dispatch_event(&rep->reply);
                                    enqueue_event(rep);
                                    rep = NULL;
                                    break;
                        }
                  }
            }
            if (hup) {
                  int rc;
                  hup = 0;
                  rc = audit_request_signal_info(fd);
                  if (rc < 0)
                      send_audit_event(AUDIT_DAEMON_CONFIG, 
                        "auditd error getting hup info - no change, sending auid=? pid=? subj=? res=failed");
                  else
                        hup_info_requested = 1;
            }
            if (rot) {
                  int rc;
                  rot = 0;
                  rc = audit_request_signal_info(fd);
                  if (rc < 0)
                      send_audit_event(AUDIT_DAEMON_ROTATE, 
                        "auditd error getting usr1 info - no change, sending auid=? pid=? subj=? res=failed");
                  else
                        usr1_info_requested = 1;
            }
            if (stop) {
                  /* Write message to log that we are going down */
                  int rc;

                  rc = audit_request_signal_info(fd);
                  if (rc > 0) {
                        struct audit_reply trep;

                        rc = get_reply(fd, &trep, rc);
                        if (rc > 0) {
                              char txt[MAX_AUDIT_MESSAGE_LENGTH];
                              snprintf(txt, sizeof(txt),
         "auditd normal halt, sending auid=%u pid=%d subj=%s res=success",
                                    trep.signal_info->uid,
                                    trep.signal_info->pid, 
                                    trep.signal_info->ctx); 
                              send_audit_event(AUDIT_DAEMON_END, txt);
                        } 
                  } 
                  if (rc <= 0)
                      send_audit_event(AUDIT_DAEMON_END, 
            "auditd normal halt, sending auid=? pid=? subj=? res=success");
                  free(rep);
                  shutdown_dispatcher();
                  break;
            }
      }

      close_down();
      free_config(&config);

      return 0;
}

static void close_down(void)
{
      struct sigaction sa;

      /* We are going down. Give the event thread a chance to shutdown.
         Just in case it hangs, set a timer to get us out of trouble. */
      sa.sa_flags = 0 ;
      sigemptyset( &sa.sa_mask ) ;
      sa.sa_handler = thread_killer;
      sigaction( SIGALRM, &sa, NULL );
      shutdown_events();
}


/*
 * A clean exit means : 
 * 1) we log that we are going down
 * 2) deregister with kernel
 * 3) close the netlink socket
 */
static void clean_exit(void)
{
      audit_msg(LOG_INFO, "The audit daemon is exiting.");
      if (fd >= 0) {
            audit_set_pid(fd, 0, WAIT_NO);
            audit_close(fd);
      }
      if (pidfile)
            unlink(pidfile);
      closelog();
}

/*
 * This function is used to get the reply for term info.
 * Returns 1 on success & -1 on failure.
 */
static int get_reply(int fd, struct audit_reply *rep, int seq)
{
        int rc, i;
        int timeout = 30; /* tenths of seconds */

      for (i = 0; i < timeout; i++) {
            struct timeval t;
            fd_set read_mask;

            t.tv_sec  = 0;
            t.tv_usec = 100000; /* .1 second */
            FD_ZERO(&read_mask);
            FD_SET(fd, &read_mask);
            do {
                  rc = select(fd+1, &read_mask, NULL, NULL, &t);
            } while (rc < 0 && errno == EINTR);
            rc = audit_get_reply(fd, rep, 
                  GET_REPLY_NONBLOCKING, 0);
            if (rc > 0) {
                  /* Don't make decisions based on wrong packet */
                  if (rep->nlh->nlmsg_seq != seq)
                        continue;

                  /* If its not what we are expecting, keep looping */
                  if (rep->type == AUDIT_SIGNAL_INFO)
                        return 1;

                  /* If we get done or error, break out */
                  if (rep->type == NLMSG_DONE || rep->type == NLMSG_ERROR)
                        break;
            }
      }
      return -1;
}


Generated by  Doxygen 1.6.0   Back to index