/* audispd.c -- * Copyright 2007-08,2013 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "audispd-config.h" #include "audispd-pconfig.h" #include "audispd-llist.h" #include "audispd-builtins.h" #include "queue.h" #include "libaudit.h" /* Global Data */ volatile int stop = 0; volatile int hup = 0; /* Local data */ static daemon_conf_t daemon_config; static conf_llist plugin_conf; static int audit_fd; static pthread_t inbound_thread; static const char *config_file = "/etc/audisp/audispd.conf"; static const char *plugin_dir = "/etc/audisp/plugins.d/"; /* Local function prototypes */ static void signal_plugins(int sig); static int event_loop(void); static int safe_exec(plugin_conf_t *conf); static void *inbound_thread_main(void *arg); static void process_inbound_event(int fd); /* * SIGTERM handler */ static void term_handler( int sig ) { stop = 1; } /* * SIGCHLD handler */ static void child_handler( int sig ) { int status; pid_t pid; pid = waitpid(-1, &status, WNOHANG); if (pid > 0) { // Mark the child pid as 0 in the configs lnode *tpconf; plist_first(&plugin_conf); tpconf = plist_get_cur(&plugin_conf); while (tpconf) { if (tpconf->p && tpconf->p->pid == pid) { tpconf->p->pid = 0; break; } tpconf = plist_next(&plugin_conf); } } } /* * SIGHUP handler: re-read config */ static void hup_handler( int sig ) { hup = 1; } /* * SIGALRM handler - help force exit when terminating daemon */ static void alarm_handler( int sig ) { pthread_cancel(inbound_thread); raise(SIGTERM); } static int count_dots(const char *s) { const char *ptr; int cnt = 0; while ((ptr = strchr(s, '.'))) { cnt++; s = ptr + 1; } return cnt; } static void load_plugin_conf(conf_llist *plugin) { DIR *d; /* init plugin list */ plist_create(plugin); /* read configs */ d = opendir(plugin_dir); if (d) { struct dirent *e; while ((e = readdir(d))) { plugin_conf_t config; char fname[PATH_MAX]; // Don't run backup files, hidden files, or dirs if (e->d_name[0] == '.' || count_dots(e->d_name) > 1) continue; snprintf(fname, sizeof(fname), "%s%s", plugin_dir, e->d_name); clear_pconfig(&config); if (load_pconfig(&config, fname) == 0) { /* Push onto config list only if active */ if (config.active == A_YES) plist_append(plugin, &config); else free_pconfig(&config); } else syslog(LOG_ERR, "Skipping %s plugin due to errors", e->d_name); } closedir(d); } } static int start_one_plugin(lnode *conf) { if (conf->p->restart_cnt > daemon_config.max_restarts) return 1; if (conf->p->type == S_BUILTIN) start_builtin(conf->p); else if (conf->p->type == S_ALWAYS) { if (safe_exec(conf->p)) { syslog(LOG_ERR, "Error running %s (%s) continuing without it", conf->p->path, strerror(errno)); conf->p->active = A_NO; return 0; } /* Close the parent's read side */ close(conf->p->plug_pipe[0]); conf->p->plug_pipe[0] = -1; /* Avoid leaking descriptor */ fcntl(conf->p->plug_pipe[1], F_SETFD, FD_CLOEXEC); } return 1; } static int start_plugins(conf_llist *plugin) { /* spawn children */ lnode *conf; int active = 0; plist_first(plugin); conf = plist_get_cur(plugin); if (conf == NULL || conf->p == NULL) return active; do { if (conf->p && conf->p->active == A_YES) { if (start_one_plugin(conf)) active++; } } while ((conf = plist_next(plugin))); return active; } static int reconfigure(void) { int rc; daemon_conf_t tdc; conf_llist tmp_plugin; lnode *tpconf; /* Read new daemon config */ rc = load_config(&tdc, config_file); if (rc == 0) { if (tdc.q_depth > daemon_config.q_depth) { increase_queue_depth(tdc.q_depth); daemon_config.q_depth = tdc.q_depth; } daemon_config.overflow_action = tdc.overflow_action; reset_suspended(); /* We just fill these in because they are used by this * same thread when we return */ daemon_config.node_name_format = tdc.node_name_format; free((char *)daemon_config.name); daemon_config.name = tdc.name; } /* The idea for handling SIGHUP to children goes like this: * 1) load the current config in temp list * 2) mark all in real list unchecked * 3) for each one in tmp list, scan old list * 4) if new, start it, append to list, mark done * 5) else check if there was a change to active state * 6) if so, copy config over and start * 7) If no change, send sighup to non-builtins and mark done * 8) Finally, scan real list for unchecked, terminate and deactivate */ syslog(LOG_INFO, "Starting reconfigure"); load_plugin_conf(&tmp_plugin); plist_mark_all_unchecked(&plugin_conf); plist_first(&tmp_plugin); tpconf = plist_get_cur(&tmp_plugin); while (tpconf && tpconf->p) { lnode *opconf; opconf = plist_find_name(&plugin_conf, tpconf->p->name); if (opconf == NULL) { /* We have a new service */ if (tpconf->p->active == A_YES) { tpconf->p->checked = 1; plist_last(&plugin_conf); plist_append(&plugin_conf, tpconf->p); free(tpconf->p); tpconf->p = NULL; start_one_plugin(plist_get_cur(&plugin_conf)); } } else { if (opconf->p->active == tpconf->p->active) { /* If active and no state change, sighup it */ if (opconf->p->type == S_ALWAYS && opconf->p->active == A_YES) { if (opconf->p->inode==tpconf->p->inode) kill(opconf->p->pid, SIGHUP); else { /* Binary changed, restart */ syslog(LOG_INFO, "Restarting %s since binary changed", opconf->p->path); kill(opconf->p->pid, SIGTERM); usleep(50000); // 50 msecs close(opconf->p->plug_pipe[1]); opconf->p->plug_pipe[1] = -1; opconf->p->pid = 0; start_one_plugin(opconf); opconf->p->inode = tpconf->p->inode; } } opconf->p->checked = 1; } else { /* A change in state */ if (tpconf->p->active == A_YES) { /* starting - copy config and exec */ free_pconfig(opconf->p); free(opconf->p); opconf->p = tpconf->p; opconf->p->checked = 1; start_one_plugin(opconf); tpconf->p = NULL; } } } tpconf = plist_next(&tmp_plugin); } /* Now see what's left over */ while ( (tpconf = plist_find_unchecked(&plugin_conf)) ) { /* Anything not checked is something removed from the config */ tpconf->p->active = A_NO; syslog(LOG_INFO, "Terminating %s because its now inactive", tpconf->p->path); if (tpconf->p->type == S_ALWAYS) { kill(tpconf->p->pid, SIGTERM); close(tpconf->p->plug_pipe[1]); } else stop_builtin(tpconf->p); tpconf->p->plug_pipe[1] = -1; tpconf->p->pid = 0; tpconf->p->checked = 1; } /* Release memory from temp config */ plist_first(&tmp_plugin); tpconf = plist_get_cur(&tmp_plugin); while (tpconf) { free_pconfig(tpconf->p); tpconf = plist_next(&tmp_plugin); } plist_clear(&tmp_plugin); return plist_count_active(&plugin_conf); } int main(int argc, char *argv[]) { lnode *conf; struct sigaction sa; int i; #ifndef DEBUG /* Make sure we are root */ if (getuid() != 0) { fprintf(stderr, "You must be root to run this program.\n"); return 4; } #endif set_aumessage_mode(MSG_SYSLOG, DBG_YES); /* Clear any procmask set by libev */ sigfillset (&sa.sa_mask); sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0); /* Register sighandlers */ sa.sa_flags = 0; sigemptyset(&sa.sa_mask); /* Ignore all signals by default */ sa.sa_handler = SIG_IGN; for (i=1; i= 0) { if (dup2(0, i) < 0 || dup2(1, i) < 0 || dup2(2, i) < 0) { syslog(LOG_ERR, "Failed duping /dev/null %s, exiting", strerror(errno)); return 1; } close(i); } else { syslog(LOG_ERR, "Failed opening /dev/null %s, exiting", strerror(errno)); return 1; } if (fcntl(audit_fd, F_SETFD, FD_CLOEXEC) < 0) { syslog(LOG_ERR, "Failed protecting input %s, exiting", strerror(errno)); return 1; } /* init the daemon's config */ if (load_config(&daemon_config, config_file)) return 6; load_plugin_conf(&plugin_conf); /* if no plugins - exit */ if (plist_count(&plugin_conf) == 0) { syslog(LOG_NOTICE, "No plugins found, exiting"); return 0; } /* Plugins are started with the auditd priority */ i = start_plugins(&plugin_conf); /* Now boost priority to make sure we are getting time slices */ if (daemon_config.priority_boost != 0) { int rc; errno = 0; rc = nice((int)-daemon_config.priority_boost); if (rc == -1 && errno) { syslog(LOG_ERR, "Cannot change priority (%s)", strerror(errno)); /* Stay alive as this is better than stopping */ } } /* Let the queue initialize */ init_queue(daemon_config.q_depth); syslog(LOG_INFO, "audispd initialized with q_depth=%d and %d active plugins", daemon_config.q_depth, i); /* Tell it to poll the audit fd */ if (add_event(audit_fd, process_inbound_event) < 0) { syslog(LOG_ERR, "Cannot add event, exiting"); return 1; } /* Create inbound thread */ pthread_create(&inbound_thread, NULL, inbound_thread_main, NULL); /* Start event loop */ while (event_loop()) { hup = 0; if (reconfigure() == 0) { syslog(LOG_INFO, "After reconfigure, there are no active plugins, exiting"); break; } } /* Tell plugins we are going down */ signal_plugins(SIGTERM); /* Cleanup builtin plugins */ destroy_af_unix(); destroy_syslog(); /* Give it 5 seconds to clear the queue */ alarm(5); pthread_join(inbound_thread, NULL); /* Release configs */ plist_first(&plugin_conf); conf = plist_get_cur(&plugin_conf); while (conf) { free_pconfig(conf->p); conf = plist_next(&plugin_conf); } plist_clear(&plugin_conf); /* Cleanup the queue */ destroy_queue(); free_config(&daemon_config); return 0; } static int safe_exec(plugin_conf_t *conf) { char *argv[MAX_PLUGIN_ARGS+2]; int pid, i; /* Set up IPC with child */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, conf->plug_pipe) != 0) return -1; pid = fork(); if (pid > 0) { conf->pid = pid; return 0; /* Parent...normal exit */ } if (pid < 0) { close(conf->plug_pipe[0]); close(conf->plug_pipe[1]); conf->pid = 0; return -1; /* Failed to fork */ } /* Set up comm with child */ dup2(conf->plug_pipe[0], 0); for (i=3; i<24; i++) /* Arbitrary number */ close(i); /* Child */ argv[0] = (char *)conf->path; for (i=1; i<(MAX_PLUGIN_ARGS+1); i++) argv[i] = conf->args[i]; argv[i] = NULL; execve(conf->path, argv, NULL); exit(1); /* Failed to exec */ } static void signal_plugins(int sig) { lnode *conf; plist_first(&plugin_conf); conf = plist_get_cur(&plugin_conf); while (conf) { if (conf->p && conf->p->pid && conf->p->type == S_ALWAYS) kill(conf->p->pid, sig); conf = plist_next(&plugin_conf); } } static int write_to_plugin(event_t *e, const char *string, size_t string_len, lnode *conf) { int rc; if (conf->p->format == F_STRING) { do { rc = write(conf->p->plug_pipe[1], string, string_len); } while (rc < 0 && errno == EINTR); } else { struct iovec vec[2]; vec[0].iov_base = &e->hdr; vec[0].iov_len = sizeof(struct audit_dispatcher_header); vec[1].iov_base = e->data; vec[1].iov_len = MAX_AUDIT_MESSAGE_LENGTH; do { rc = writev(conf->p->plug_pipe[1], vec, 2); } while (rc < 0 && errno == EINTR); } return rc; } /* Returns 0 on stop, and 1 on HUP */ static int event_loop(void) { char *name = NULL, tmp_name[255]; /* Get the host name representation */ switch (daemon_config.node_name_format) { case N_NONE: break; case N_HOSTNAME: if (gethostname(tmp_name, sizeof(tmp_name))) { syslog(LOG_ERR, "Unable to get machine name"); name = strdup("?"); } else name = strdup(tmp_name); break; case N_USER: if (daemon_config.name) name = strdup(daemon_config.name); else { syslog(LOG_ERR, "User defined name missing"); name = strdup("?"); } break; case N_FQD: if (gethostname(tmp_name, sizeof(tmp_name))) { syslog(LOG_ERR, "Unable to get machine name"); name = strdup("?"); } else { int rc; struct addrinfo *ai; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG | AI_CANONNAME; hints.ai_socktype = SOCK_STREAM; rc = getaddrinfo(tmp_name, NULL, &hints, &ai); if (rc != 0) { syslog(LOG_ERR, "Cannot resolve hostname %s (%s)", tmp_name, gai_strerror(rc)); name = strdup("?"); break; } name = strdup(ai->ai_canonname); freeaddrinfo(ai); } break; case N_NUMERIC: if (gethostname(tmp_name, sizeof(tmp_name))) { syslog(LOG_ERR, "Unable to get machine name"); name = strdup("?"); } else { int rc; struct addrinfo *ai; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE; hints.ai_socktype = SOCK_STREAM; rc = getaddrinfo(tmp_name, NULL, &hints, &ai); if (rc != 0) { syslog(LOG_ERR, "Cannot resolve hostname %s (%s)", tmp_name, gai_strerror(rc)); name = strdup("?"); break; } inet_ntop(ai->ai_family, ai->ai_family == AF_INET ? (void *) &((struct sockaddr_in *)ai->ai_addr)->sin_addr : (void *) &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr, tmp_name, INET6_ADDRSTRLEN); freeaddrinfo(ai); name = strdup(tmp_name); } break; } /* Figure out the format for the af_unix socket */ while (stop == 0) { event_t *e; const char *type; char *v, *ptr, unknown[32]; unsigned int len; lnode *conf; /* This is where we block until we have an event */ e = dequeue(); if (e == NULL) { if (hup) { free(name); return 1; } continue; } /* Get the event formatted */ type = audit_msg_type_to_name(e->hdr.type); if (type == NULL) { snprintf(unknown, sizeof(unknown), "UNKNOWN[%d]", e->hdr.type); type = unknown; } if (daemon_config.node_name_format != N_NONE) { len = asprintf(&v, "node=%s type=%s msg=%.*s\n", name, type, e->hdr.size, e->data); } else len = asprintf(&v, "type=%s msg=%.*s\n", type, e->hdr.size, e->data); if (len <= 0) { v = NULL; free(e); /* Either corrupted event or no memory */ continue; } /* Strip newlines from event record */ ptr = v; while ((ptr = strchr(ptr, 0x0A)) != NULL) { if (ptr != &v[len-1]) *ptr = ' '; else break; /* Done - exit loop */ } /* Distribute event to the plugins */ plist_first(&plugin_conf); conf = plist_get_cur(&plugin_conf); do { if (conf == NULL || conf->p == NULL) continue; if (conf->p->active == A_NO || stop) continue; /* Now send the event to the right child */ if (conf->p->type == S_SYSLOG) send_syslog(v); else if (conf->p->type == S_AF_UNIX) { if (conf->p->format == F_STRING) send_af_unix_string(v, len); else send_af_unix_binary(e); } else if (conf->p->type == S_ALWAYS && !stop) { int rc; rc = write_to_plugin(e, v, len, conf); if (rc < 0 && errno == EPIPE) { /* Child disappeared ? */ syslog(LOG_ERR, "plugin %s terminated unexpectedly", conf->p->path); conf->p->pid = 0; conf->p->restart_cnt++; if (conf->p->restart_cnt > daemon_config.max_restarts) { syslog(LOG_ERR, "plugin %s has exceeded max_restarts", conf->p->path); } close(conf->p->plug_pipe[1]); conf->p->plug_pipe[1] = -1; conf->p->active = A_NO; if (!stop && start_one_plugin(conf)) { rc = write_to_plugin(e, v, len, conf); syslog(LOG_NOTICE, "plugin %s was restarted", conf->p->path); conf->p->active = A_YES; } } } } while (!stop && (conf = plist_next(&plugin_conf))); /* Done with the memory...release it */ free(v); free(e); if (hup) break; } free(name); if (stop) return 0; else return 1; } static struct pollfd pfd[4]; static poll_callback_ptr pfd_cb[4]; static volatile int pfd_cnt=0; int add_event(int fd, poll_callback_ptr cb) { if (pfd_cnt > 3) return -1; pfd[pfd_cnt].fd = fd; pfd[pfd_cnt].events = POLLIN; pfd[pfd_cnt].revents = 0; pfd_cb[pfd_cnt] = cb; pfd_cnt++; return 0; } int remove_event(int fd) { int start, i; if (pfd_cnt == 0) return -1; for (start=0; start < pfd_cnt; start++) { if (pfd[start].fd == fd) break; } for (i=start; i<(pfd_cnt-1); i++) { pfd[i].events = pfd[i+1].events; pfd[i].revents = pfd[i+1].revents; pfd[i].fd = pfd[i+1].fd; pfd_cb[i] = pfd_cb[i+1]; } pfd_cnt--; return 0; } /* inbound thread - enqueue inbound data to intermediate table */ static void *inbound_thread_main(void *arg) { while (stop == 0) { int rc; if (hup) nudge_queue(); do { rc = poll(pfd, pfd_cnt, 20000); /* 20 sec */ } while (rc < 0 && errno == EAGAIN && stop == 0 && hup == 0); if (rc == 0) continue; /* Event readable... */ if (rc > 0) { /* Figure out the fd that is ready and call */ int i = 0; while (i < pfd_cnt) { if (pfd[i].revents & POLLIN) pfd_cb[i](pfd[i].fd); i++; } } } /* make sure event loop wakes up */ nudge_queue(); return NULL; } static void process_inbound_event(int fd) { int rc; struct iovec vec; event_t *e = malloc(sizeof(event_t)); if (e == NULL) return; memset(e, 0, sizeof(event_t)); /* Get header first. It is fixed size */ vec.iov_base = &e->hdr; vec.iov_len = sizeof(struct audit_dispatcher_header); do { rc = readv(fd, &vec, 1); } while (rc < 0 && errno == EINTR); if (rc <= 0) { if (rc == 0) stop = 1; // End of File free(e); return; } if (rc > 0) { /* Sanity check */ if (e->hdr.ver != AUDISP_PROTOCOL_VER || e->hdr.hlen != sizeof(e->hdr) || e->hdr.size > MAX_AUDIT_MESSAGE_LENGTH) { free(e); syslog(LOG_ERR, "Dispatcher protocol mismatch, exiting"); exit(1); } /* Next payload */ vec.iov_base = e->data; vec.iov_len = e->hdr.size; do { rc = readv(fd, &vec, 1); } while (rc < 0 && errno == EINTR); if (rc > 0) enqueue(e, &daemon_config); else { if (rc == 0) stop = 1; // End of File free(e); } } }