summaryrefslogtreecommitdiff
path: root/usr/src/cmd/auditd
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/auditd
downloadillumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/auditd')
-rw-r--r--usr/src/cmd/auditd/Makefile87
-rw-r--r--usr/src/cmd/auditd/audit_sig_infc.h51
-rw-r--r--usr/src/cmd/auditd/auditd.c841
-rw-r--r--usr/src/cmd/auditd/auditd.xml152
-rw-r--r--usr/src/cmd/auditd/doorway.c1317
-rw-r--r--usr/src/cmd/auditd/plugin.h102
-rw-r--r--usr/src/cmd/auditd/queue.c172
-rw-r--r--usr/src/cmd/auditd/queue.h89
-rw-r--r--usr/src/cmd/auditd/svc-auditd62
9 files changed, 2873 insertions, 0 deletions
diff --git a/usr/src/cmd/auditd/Makefile b/usr/src/cmd/auditd/Makefile
new file mode 100644
index 0000000000..509ecb80cf
--- /dev/null
+++ b/usr/src/cmd/auditd/Makefile
@@ -0,0 +1,87 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+PROG = auditd
+
+include ../Makefile.cmd
+
+MANIFEST = auditd.xml
+ROOTMANIFESTDIR = $(ROOTSVCSYSTEM)
+ROOTMETHOD = $(ROOTLIBSVCMETHOD)/svc-auditd
+
+LIBBSM = $(SRC)/lib/libbsm/common
+
+CPPFLAGS += -D_REENTRANT
+CPPFLAGS += -I$(LIBBSM)
+
+LINTFLAGS += -erroff=E_NAME_USED_NOT_DEF2
+LINTFLAGS += -m
+
+TEXT_DOMAIN=SUNW_OST_OSCMD
+
+LDLIBS += -lbsm -lnsl -lsecdb -lsocket
+
+OBJS= auditd.o doorway.o queue.o
+SRCS= $(OBJS:%.o=%.c)
+
+$(ROOTSVCSYSTEM)/auditd.xml := OWNER = root
+$(ROOTSVCSYSTEM)/auditd.xml := GROUP = sys
+$(ROOTSVCSYSTEM)/auditd.xml := FILEMODE = 0444
+
+$(ROOTLIBSVCMETHOD)/svc-auditd := OWNER = root
+$(ROOTLIBSVCMETHOD)/svc-auditd := GROUP = bin
+$(ROOTLIBSVCMETHOD)/svc-auditd := FILEMODE = 0555
+
+.KEEP_STATE:
+
+all: $(PROG) $(SUBDIRS)
+
+install: all $(ROOTUSRSBINPROG) $(SUBDIRS) \
+ $(ROOTMANIFEST) $(ROOTMETHOD)
+
+$(PROG): $(OBJS)
+ $(LINK.c) $(OBJS) -o $@ $(LDLIBS)
+ $(POST_PROCESS)
+
+lint: lint_SRCS
+
+clean: $(SUBDIRS)
+
+clobber: $(SUBDIRS) local_clobber
+
+local_clobber:
+ rm -f $(OBJS) $(PROG).po
+
+check: $(CHKMANIFEST)
+
+$(SUBDIRS): FRC
+ @cd $@; pwd; $(MAKE) $(TARGET)
+
+FRC:
+
+include ../Makefile.targ
diff --git a/usr/src/cmd/auditd/audit_sig_infc.h b/usr/src/cmd/auditd/audit_sig_infc.h
new file mode 100644
index 0000000000..68f58964a3
--- /dev/null
+++ b/usr/src/cmd/auditd/audit_sig_infc.h
@@ -0,0 +1,51 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * defines auditd interface for cmd/audit; project private.
+ */
+
+#ifndef _AUDIT_SIG_INFC_H
+#define _AUDIT_SIG_INFC_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <signal.h>
+
+/*
+ * Signals
+ */
+#define AU_SIG_READ_CONTROL SIGHUP /* audit -s */
+#define AU_SIG_NEXT_DIR SIGUSR1 /* audit -n */
+#define AU_SIG_DISABLE SIGTERM /* audit -t */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _AUDIT_SIG_INFC_H */
diff --git a/usr/src/cmd/auditd/auditd.c b/usr/src/cmd/auditd/auditd.c
new file mode 100644
index 0000000000..9d392df61d
--- /dev/null
+++ b/usr/src/cmd/auditd/auditd.c
@@ -0,0 +1,841 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/* Audit daemon server */
+/*
+ * These routines make up the audit daemon server. This daemon, called
+ * auditd, handles the user level parts of auditing. It receives buffered
+ * audit records (usually one or more per buffer, potentially less than
+ * one) and passes them to one or more plugins for processing.
+ *
+ * The major interrupts are AU_SIG_READ_CONTROL (start over),
+ * AU_SIG_DISABLE (start shutting down), SIGALRM (quit), and
+ * AU_SIG_NEXT_DIR (start a new audit log file). SIGTERM (the implementation
+ * value of AU_SIG_DISABLE) is also used for the child to tell the parent
+ * that audit is ready.
+ *
+ * Configuration data comes from /etc/security/audit_control and the auditon
+ * system call.
+ *
+ * The major errors are EBUSY (auditing is already in use) and EINTR
+ * (one of the above signals was received). File space errors are
+ * handled by the audit_binfile plugin
+ */
+
+#define DEBUG 0
+#define MEM_TEST 0 /* set to one to generate core dump on exit */
+
+#include <assert.h>
+#include <bsm/audit.h>
+#include <bsm/audit_record.h>
+#include <bsm/libbsm.h>
+#include <fcntl.h>
+#include <libintl.h>
+#include <locale.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <secdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/file.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+#include "plugin.h"
+#include "audit_sig_infc.h"
+#include <audit_plugin.h>
+
+#if !defined(TEXT_DOMAIN)
+#define TEXT_DOMAIN "SUNW_OST_OSCMD"
+#endif
+/*
+ * After we get a AU_SIG_DISABLE, we want to set a timer for 2 seconds
+ * and let c2audit write as many records as it can until the timer
+ * goes off(at which point it returns to auditd with SIGALRM). If any
+ * other signals are received during that time, we call
+ * __audit_dowarn() to indicate that the queue may not have been fully
+ * flushed.
+ */
+#define ALRM_TIME 2
+#define SLEEP_TIME 20 /* # of seconds to sleep in all hard loop */
+
+#if DEBUG
+#define DPRINT(x) {(void) fprintf x; }
+static FILE *dbfp; /* debug file */
+#else
+#define DPRINT(x)
+#endif /* DEBUG */
+
+static plugin_t *binfile = NULL;
+
+static int turn_audit_on = AUC_AUDITING;
+static int turn_audit_off = AUC_NOAUDIT;
+
+static int running = 1;
+
+/*
+ * GLOBALS:
+ */
+plugin_t *plugin_head = NULL;
+static thr_data_t main_thr; /* auditd thread (0) */
+pthread_mutex_t plugin_mutex; /* for plugin_t list */
+
+static int caught_alrm = 0; /* number of SIGALRMs pending */
+static int caught_readc = 0; /* number of AU_SIG_READ_CONTROLs */
+static int caught_term = 0; /* number of AU_SIG_DISABLEs pending */
+static int caught_nextd = 0; /* number of AU_SIG_NEXT_DIRs pending */
+
+static int reset_list = 1; /* 1 to re-read audit_control */
+static int reset_file = 1; /* 1 to close/open binary log */
+
+static int auditing_set = 0; /* 1 if auditon(A_SETCOND, on... */
+
+static void my_sleep();
+static void signal_thread();
+static void loadauditlist();
+static void block_signals();
+static int do_sethost();
+
+/* common exit function */
+void
+auditd_exit(int status)
+{
+#if MEM_TEST
+ sigset_t set;
+
+ DPRINT((dbfp, "mem_test intentional abort (status=%d)\n",
+ status));
+ abort();
+#endif
+ DPRINT((dbfp, "%ld exit status = %d auditing_set = %d\n",
+ getpid(), status, auditing_set));
+
+ if (auditing_set)
+ (void) auditon(A_SETCOND, (caddr_t)&turn_audit_off,
+ (int)sizeof (int));
+
+ exit(status);
+}
+
+/* ARGSUSED */
+main(int argc, char *argv[])
+{
+ auditinfo_addr_t as_null; /* audit state to set */
+ au_id_t auid;
+ pthread_t tid;
+ plugin_t *p;
+ pid_t pid;
+
+#if DEBUG
+ /* LINTED */
+ char *envp;
+ dbfp = __auditd_debug_file_open();
+#endif
+ (void) setsid();
+
+ /* Internationalization */
+ (void) setlocale(LC_ALL, "");
+ (void) textdomain(TEXT_DOMAIN);
+
+ /*
+ * Set the audit host-id.
+ */
+ if (do_sethost() != 0) {
+ __audit_dowarn("nostart", "", 0);
+ auditd_exit(1);
+ }
+
+ /*
+ * Turn off all auditing for this process.
+ */
+ if (getaudit_addr(&as_null, sizeof (as_null)) == -1) {
+ __audit_dowarn("nostart", "", 0);
+ auditd_exit(2);
+ }
+ as_null.ai_mask.as_success = 0;
+ as_null.ai_mask.as_failure = 0;
+ (void) setaudit_addr(&as_null, sizeof (as_null));
+ auid = AU_NOAUDITID;
+ (void) setauid(&auid);
+ /*
+ * Set the audit state flag to AUDITING.
+ */
+ if (auditon(A_SETCOND, (caddr_t)&turn_audit_on, (int)sizeof (int)) !=
+ 0) {
+ DPRINT((dbfp, "auditon(A_SETCOND...) failed (exit)\n"));
+ __audit_dowarn("nostart", "", 0);
+ auditd_exit(7);
+ }
+
+ block_signals();
+
+#if DEBUG
+ /* output to dbfp shouldn't be duplicated by parent and child */
+ (void) fflush(dbfp);
+#endif
+ /*
+ * wait for "ready" signal before exit -- for greenline
+ */
+ if (fork()) {
+ sigset_t set;
+ int signal_caught = 0;
+
+ (void) sigemptyset(&set);
+ (void) sigaddset(&set, AU_SIG_DISABLE);
+
+ while (signal_caught != AU_SIG_DISABLE)
+ signal_caught = sigwait(&set);
+
+ DPRINT((dbfp, "init complete: parent can now exit\n"));
+
+ auditd_exit(0);
+ }
+ pid = getppid();
+
+ auditing_set = 1;
+
+#if DEBUG && MEM_TEST
+ envp = getenv("UMEM_DEBUG");
+ if (envp != NULL)
+ DPRINT((dbfp, "UMEM_DEBUG=%s\n", envp));
+ envp = getenv("UMEM_LOGGING");
+ if (envp != NULL)
+ DPRINT((dbfp, "UMEM_LOGGING=%s\n", envp));
+#endif
+ DPRINT((dbfp, "auditd pid=%ld\n", getpid()));
+
+ /* thread 0 sync */
+ (void) pthread_mutex_init(&(main_thr.thd_mutex), NULL);
+ (void) pthread_cond_init(&(main_thr.thd_cv), NULL);
+ (void) pthread_mutex_init(&plugin_mutex, NULL);
+ /*
+ * Set up a separate thread for signal handling.
+ */
+ if (pthread_create(&tid, NULL, (void *(*)(void *))signal_thread,
+ NULL)) {
+ (void) fprintf(stderr, gettext(
+ "auditd can't create a thread\n"));
+ auditd_exit(3);
+ }
+ /*
+ * Set the umask so that only audit or other users in the audit group
+ * can get to the files created by auditd.
+ */
+ (void) umask(007);
+
+ if (__logpost("")) { /* Open the audit_data file. */
+ DPRINT((dbfp, "logpost failed\n"));
+ auditd_exit(4);
+ }
+ /*
+ * Here is the main body of the audit daemon. running == 0 means that
+ * after flushing out the audit queue, it is time to exit in response to
+ * AU_SIG_DISABLE
+ */
+ while (running) {
+ /*
+ * Read audit_control and create plugin lists.
+ *
+ * loadauditlist() and auditd_thread_init() are called
+ * while under the plugin_mutex lock to avoid a race
+ * with unload_plugin().
+ */
+ if (reset_list || reset_file) {
+ (void) pthread_mutex_lock(&plugin_mutex);
+ if (reset_list)
+ loadauditlist();
+
+ if (auditd_thread_init()) {
+ auditd_thread_close();
+ /* continue; wait for audit -s */
+ }
+ (void) pthread_mutex_unlock(&plugin_mutex);
+ reset_list = 0;
+ }
+ /*
+ * tell parent I'm running whether or not the initialization
+ * actually worked. The failure case is to wait for an
+ * audit -n or audit -s to fix the problem.
+ */
+ if (pid != 0) {
+ (void) kill(pid, AU_SIG_DISABLE);
+ pid = 0;
+ }
+ /*
+ * thread_signal() signals main (this thread) when
+ * it has received a signal.
+ */
+ DPRINT((dbfp, "main thread is waiting\n"));
+ (void) pthread_mutex_lock(&(main_thr.thd_mutex));
+
+ if (!(caught_readc || caught_term || caught_alrm ||
+ caught_nextd))
+ (void) pthread_cond_wait(&(main_thr.thd_cv),
+ &(main_thr.thd_mutex));
+ (void) pthread_mutex_unlock(&(main_thr.thd_mutex));
+ /*
+ * Got here because a signal came in.
+ * Since we may have gotten more than one, we assume a
+ * priority scheme with SIGALRM being the most
+ * significant.
+ */
+ if (caught_alrm) {
+ /*
+ * We have returned from our timed wait for
+ * c2audit to calm down. We need to really shut
+ * down here.
+ */
+ caught_alrm = 0;
+ running = 0; /* shut down now */
+ } else if (caught_term) {
+ /*
+ * we are going to shut down, but need to
+ * allow time for the audit queues in
+ * c2audit and for the threads to empty.
+ */
+
+ p = plugin_head;
+ while (p != NULL) {
+ DPRINT((dbfp, "signalling thread %d\n",
+ p->plg_tid));
+ (void) pthread_mutex_lock(&(p->plg_mutex));
+ p->plg_removed = 1;
+
+ if (p->plg_initialized)
+ (void) pthread_cond_signal(
+ &(p->plg_cv));
+
+ (void) pthread_mutex_unlock(&(p->plg_mutex));
+ p = p->plg_next;
+ }
+
+ caught_alrm = 0;
+ caught_readc = 0;
+ caught_term = 0;
+ caught_nextd = 0;
+
+ DPRINT((dbfp,
+ "main thread is pausing before exit.\n"));
+ (void) pthread_mutex_lock(&(main_thr.thd_mutex));
+ caught_alrm = 0;
+ (void) alarm(ALRM_TIME);
+ while (!caught_alrm)
+ (void) pthread_cond_wait(&(main_thr.thd_cv),
+ &(main_thr.thd_mutex));
+
+ (void) pthread_mutex_unlock(&(main_thr.thd_mutex));
+
+ running = 0; /* Close down auditing and exit */
+ } else if (caught_readc) {
+ /*
+ * if both hup and usr1 are caught, the logic in
+ * loadauditlist() results in hup winning. The
+ * result will be that the audit file is not rolled
+ * over unless audit_control actually changed.
+ *
+ * They want to reread the audit_control file.
+ * Set reset_list which will return us to the
+ * main while loop in the main routine.
+ */
+ caught_readc = 0;
+ reset_list = 1;
+ } else if (caught_nextd) {
+ /*
+ * This is a special case for the binfile
+ * plugin. (audit -n) NULL out kvlist
+ * so binfile won't re-read audit_control
+ */
+ caught_nextd = 0;
+ reset_file = 1;
+ if (binfile != NULL) {
+ _kva_free(binfile->plg_kvlist);
+ binfile->plg_kvlist = NULL;
+ binfile->plg_reopen = 1;
+ }
+ }
+ } /* end while (running) */
+ auditd_thread_close();
+
+ auditd_exit(0);
+ return (0);
+}
+
+/*
+ * my_sleep - sleep for SLEEP_TIME seconds but only accept the signals
+ * that we want to accept. (Premature termination just means the
+ * caller retries more often, not a big deal.)
+ */
+
+static void
+my_sleep()
+{
+ DPRINT((dbfp, "auditd: sleeping for 20 seconds\n"));
+ /*
+ * Set timer to "sleep"
+ */
+ (void) alarm(SLEEP_TIME);
+
+ DPRINT((dbfp, "main thread is waiting for SIGALRM before exit.\n"));
+ (void) pthread_mutex_lock(&(main_thr.thd_mutex));
+ (void) pthread_cond_wait(&(main_thr.thd_cv), &(main_thr.thd_mutex));
+ (void) pthread_mutex_unlock(&(main_thr.thd_mutex));
+
+ if (caught_term) {
+ DPRINT((dbfp, "normal AU_SIG_DISABLE exit\n"));
+ /*
+ * Exit, as requested.
+ */
+ auditd_thread_close();
+ }
+ if (caught_readc)
+ reset_list = 1; /* Reread the audit_control file */
+
+ caught_readc = 0;
+ caught_nextd = 0;
+}
+
+/*
+ * search for $ISA/ in path and replace it with "" if auditd
+ * is 32 bit, else "sparcv9/" The plugin $ISA must match however
+ * auditd was compiled.
+ */
+
+static void
+isa_ified(char *path, char **newpath)
+{
+ char *p, *q;
+
+ if (((p = strchr(path, '$')) != NULL) &&
+ (strncmp("$ISA/", p, 5) == 0)) {
+ (void) memcpy(*newpath, path, p - path);
+ q = *newpath + (p - path);
+#ifdef __sparcv9
+ q += strlcpy(q, "sparcv9/", avail_length);
+#endif
+ (void) strcpy(q, p + 5);
+ } else
+ *newpath = path;
+}
+
+/*
+ * init_plugin first searches the existing plugin list to see
+ * if the plugin already has been defined; if not, it creates it
+ * and links it into the list. It returns a pointer to the found
+ * or created struct. A change of path in audit_control for a
+ * given plugin will cause a miss.
+ */
+/*
+ * for 64 bits, the path name can grow 3 bytes (minus 5 for the
+ * removed "$ISA" and plus 8 for the added "sparcv9/"
+ */
+
+#define ISA_GROW 8 - 5
+
+static plugin_t *
+init_plugin(char *name, kva_t *list, int cnt_flag)
+{
+ plugin_t *p, *q;
+ char filepath[MAXPATHLEN + 1 + ISA_GROW];
+ char *path = filepath;
+
+ if (*name != '/') {
+#ifdef __sparcv9
+ (void) strcpy(filepath, "/usr/lib/security/sparcv9/");
+#else
+ (void) strcpy(filepath, "/usr/lib/security/");
+#endif
+ if (strlcat(filepath, name, MAXPATHLEN) >= MAXPATHLEN)
+ return (NULL);
+ } else {
+ if (strlen(name) > MAXPATHLEN + ISA_GROW)
+ return (NULL);
+ isa_ified(name, &path);
+ }
+ p = plugin_head;
+ q = plugin_head;
+ while (p != NULL) {
+ if (p->plg_path != NULL) {
+ if (strcmp(p->plg_path, path) == 0) {
+ p->plg_removed = 0;
+ p->plg_to_be_removed = 0;
+ p->plg_cnt = cnt_flag;
+
+ _kva_free(p->plg_kvlist);
+ p->plg_kvlist = list;
+ p->plg_reopen = 1;
+ DPRINT((dbfp, "reusing %s\n", p->plg_path));
+ return (p);
+ }
+ }
+ q = p;
+ p = p->plg_next;
+ }
+ DPRINT((dbfp, "creating new plugin structure for %s\n", path));
+
+ p = malloc(sizeof (plugin_t));
+
+ if (p == NULL) {
+ perror("auditd");
+ return (NULL);
+ }
+ if (q == NULL)
+ plugin_head = p;
+ else
+ q->plg_next = p;
+
+ p->plg_next = NULL;
+ p->plg_initialized = 0;
+ p->plg_reopen = 1;
+ p->plg_tid = 0;
+ p->plg_removed = 0;
+ p->plg_to_be_removed = 0;
+ p->plg_tossed = 0;
+ p->plg_queued = 0;
+ p->plg_output = 0;
+ p->plg_sequence = 1;
+ p->plg_last_seq_out = 0;
+ p->plg_path = strdup(path);
+ p->plg_kvlist = list;
+ p->plg_cnt = cnt_flag;
+ p->plg_retry_time = SLEEP_TIME;
+ p->plg_qmax = 0;
+ p->plg_save_q_copy = NULL;
+
+ DPRINT((dbfp, "created plugin: %s\n", path));
+ return (p);
+}
+
+/*
+ * loadauditlist - read the directory list from the audit_control file.
+ * to determine if a binary file is to be written.
+ * - read the plugin entries from the audit_control file
+ *
+ * globals -
+ *
+ * plugin queues
+ *
+ * success is when at least one plug in is defined.
+ *
+ * set cnt policy here based on auditconfig setting. future could
+ * have a policy = {+|-}cnt entry per plugin with auditconfig providing the
+ * default.
+ */
+
+static void
+loadauditlist()
+{
+ char buf[MAXPATHLEN];
+ char *value;
+ plugin_t *p;
+ int acresult;
+ int wait_count = 0;
+ kva_t *kvlist;
+ long policy;
+ int cnt_flag;
+ struct au_qctrl kqmax;
+ au_acinfo_t *ach = NULL;
+ int got_dir = 0;
+ int have_plugin = 0;
+ char *endptr;
+
+ if (auditon(A_GETPOLICY, (char *)&policy, 0) == -1) {
+ DPRINT((dbfp, "auditon(A_GETPOLICY...) failed (exit)\n"));
+ __audit_dowarn("auditoff", "", 0);
+ auditd_thread_close();
+ auditd_exit(5);
+ }
+ cnt_flag = ((policy & AUDIT_CNT) != 0) ? 1 : 0;
+ DPRINT((dbfp, "loadauditlist: policy is to %s\n", (cnt_flag == 1) ?
+ "continue" : "block"));
+
+#if DEBUG
+ if (auditon(A_GETCOND, (caddr_t)&acresult, (int)sizeof (int)) !=
+ 0)
+ DPRINT((dbfp, "auditon(A_GETCOND...) failed (exit)\n"));
+#endif
+ DPRINT((dbfp, "audit cond = %d (1 is on)\n", acresult));
+
+
+ if (auditon(A_GETQCTRL, (char *)&kqmax, sizeof (struct au_qctrl)) !=
+ 0) {
+ DPRINT((dbfp, "auditon(A_GETQCTRL...) failed (exit)\n"));
+ __audit_dowarn("auditoff", "", 0);
+ auditd_thread_close();
+ auditd_exit(6);
+ }
+ kqmax.aq_hiwater *= 5; /* RAM is cheaper in userspace */
+ DPRINT((dbfp, "auditd: reading audit_control\n"));
+
+ p = plugin_head;
+ /*
+ * two-step on setting p->plg_removed because the input thread
+ * in doorway.c uses p->plg_removed to decide if the plugin is
+ * active.
+ */
+ while (p != NULL) {
+ DPRINT((dbfp, "loadauditlist: %X, %s previously created\n",
+ p, p->plg_path));
+ p->plg_to_be_removed = 1; /* tentative removal */
+ p = p->plg_next;
+ }
+ /*
+ * have_plugin may over count by one if both a "dir" entry
+ * and a "plugin" entry for binfile are found. All that
+ * matters is that it be zero if no plugin or dir entries
+ * are found.
+ */
+ have_plugin = 0;
+ for (;;) {
+ /* NULL == use standard path for audit_control */
+ ach = _openac(NULL);
+ /*
+ * loop until a directory entry is found (0) or eof (-1)
+ */
+ while (((acresult = _getacdir(ach, buf, sizeof (buf))) != 0) &&
+ acresult != -1) {
+ }
+ if (acresult == 0) {
+ DPRINT((dbfp,
+ "loadauditlist: "
+ "got binfile via old config syntax\n"));
+ /*
+ * A directory entry was found.
+ */
+ got_dir = 1;
+ kvlist = _str2kva("name=audit_binfile.so.1",
+ "=", ";");
+
+ p = init_plugin("audit_binfile.so.1", kvlist, cnt_flag);
+
+ if (p != NULL) {
+ binfile = p;
+ p->plg_qmax = kqmax.aq_hiwater;
+ have_plugin++;
+ }
+ }
+ /*
+ * collect plugin entries. If there is an entry for
+ * binfile.so.1, the parameters from the plugin line
+ * override those set above. For binfile, p_dir is
+ * required only if dir wasn't specified elsewhere in
+ * audit_control
+ */
+ _rewindac(ach);
+ while ((acresult = _getacplug(ach, &kvlist)) == 0) {
+ value = kva_match(kvlist, "name");
+ if (value == NULL)
+ break;
+ DPRINT((dbfp, "loadauditlist: have an entry for %s\n",
+ value));
+ p = init_plugin(value, kvlist, cnt_flag);
+ if (p == NULL)
+ continue;
+
+ if (strstr(value, "/audit_binfile.so") != NULL) {
+ binfile = p;
+ if (!got_dir &&
+ (kva_match(kvlist, "p_dir") ==
+ NULL)) {
+ __audit_dowarn("getacdir", "",
+ wait_count);
+ }
+ }
+ p->plg_qmax = kqmax.aq_hiwater; /* default */
+ value = kva_match(kvlist, "qsize");
+ if (value != NULL) {
+ long tmp;
+
+ tmp = strtol(value, &endptr, 10);
+ if (*endptr == '\0')
+ p->plg_qmax = tmp;
+ }
+ DPRINT((dbfp, "%s queue max = %d\n",
+ p->plg_path, p->plg_qmax));
+
+ have_plugin++;
+ }
+ _endac(ach);
+ if (have_plugin != 0)
+ break;
+ /*
+ * there was a problem getting the directory
+ * list or remote host info from the audit_control file
+ */
+ wait_count++;
+#if DEBUG
+ if (wait_count < 2)
+ DPRINT((dbfp,
+ "auditd: problem getting directory "
+ "/ or plugin list from audit_control.\n"));
+#endif /* DEBUG */
+ __audit_dowarn("getacdir", "", wait_count);
+ /*
+ * sleep for SLEEP_TIME seconds.
+ */
+ my_sleep();
+ } /* end for(;;) */
+
+ p = plugin_head;
+ while (p != NULL) {
+ DPRINT((dbfp, "loadauditlist: %s remove flag=%d; cnt=%d\n",
+ p->plg_path, p->plg_to_be_removed, p->plg_cnt));
+ p->plg_removed = p->plg_to_be_removed;
+ p = p->plg_next;
+ }
+}
+
+/*
+ * block signals -- thread-specific blocking of the signals expected
+ * by the main thread.
+ */
+
+static void
+block_signals()
+{
+ sigset_t set;
+
+ (void) sigfillset(&set);
+ (void) pthread_sigmask(SIG_BLOCK, &set, NULL);
+}
+
+/*
+ * signal_thread is the designated signal catcher. It wakes up the
+ * main thread whenever it receives a signal and then goes back to
+ * sleep; it does not exit. The global variables caught_* let
+ * the main thread which signal was received.
+ *
+ * The thread is created with all signals blocked.
+ */
+
+static void
+signal_thread()
+{
+ sigset_t set;
+ int signal_caught;
+
+ DPRINT((dbfp, "the signal thread is thread %d\n",
+ pthread_self()));
+
+ (void) sigemptyset(&set);
+ (void) sigaddset(&set, SIGALRM);
+ (void) sigaddset(&set, AU_SIG_DISABLE);
+ (void) sigaddset(&set, AU_SIG_READ_CONTROL);
+ (void) sigaddset(&set, AU_SIG_NEXT_DIR);
+
+ for (;;) {
+ signal_caught = sigwait(&set);
+ switch (signal_caught) {
+ case SIGALRM:
+ caught_alrm++;
+ DPRINT((dbfp, "caught SIGALRM\n"));
+ break;
+ case AU_SIG_DISABLE:
+ caught_term++;
+ DPRINT((dbfp, "caught AU_SIG_DISABLE\n"));
+ break;
+ case AU_SIG_READ_CONTROL:
+ caught_readc++;
+ DPRINT((dbfp, "caught AU_SIG_READ_CONTROL\n"));
+ break;
+ case AU_SIG_NEXT_DIR:
+ caught_nextd++;
+ DPRINT((dbfp, "caught AU_SIG_NEXT_DIR\n"));
+ break;
+ default:
+ DPRINT((dbfp, "caught unexpected signal: %d\n",
+ signal_caught));
+ break;
+ }
+ (void) pthread_cond_signal(&(main_thr.thd_cv));
+ }
+}
+
+/*
+ * do_sethost - do auditon(2) to set the audit host-id.
+ * Returns 0 if success, error code or -1 otherwise.
+ */
+static int
+do_sethost(void)
+{
+ int err;
+ char host_name[MAXHOSTNAMELEN + 1];
+ auditinfo_addr_t audit_info;
+ struct addrinfo hints;
+ struct addrinfo *ai;
+ int addr_type;
+ void *p;
+
+ /* First, get our machine name and convert to IP address */
+ if ((err = gethostname(host_name, sizeof (host_name)))) {
+ return (err);
+ }
+ (void) memset(&hints, 0, sizeof (hints));
+ hints.ai_family = PF_INET;
+ err = getaddrinfo(host_name, NULL, &hints, &ai);
+ if (err == 0) {
+ addr_type = AU_IPv4;
+ /* LINTED */
+ p = &((struct sockaddr_in *)ai->ai_addr)->sin_addr;
+ } else {
+ hints.ai_family = PF_INET6;
+ err = getaddrinfo(host_name, NULL, &hints, &ai);
+ if (err != 0) {
+ return (-1);
+ }
+ addr_type = AU_IPv6;
+ /* LINTED */
+ p = &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
+ }
+
+ /* Get current kernel audit info, and fill in the IP address */
+ if ((err = auditon(A_GETKAUDIT, (caddr_t)&audit_info,
+ sizeof (audit_info))) < 0) {
+ return (err);
+ }
+ audit_info.ai_termid.at_type = addr_type;
+ (void) memcpy(&audit_info.ai_termid.at_addr[0], p,
+ addr_type);
+
+ freeaddrinfo(ai);
+
+ /* Update the kernel audit info with new IP address */
+ if ((err = auditon(A_SETKAUDIT, (caddr_t)&audit_info,
+ sizeof (audit_info))) < 0) {
+ return (err);
+ }
+
+ return (0);
+}
diff --git a/usr/src/cmd/auditd/auditd.xml b/usr/src/cmd/auditd/auditd.xml
new file mode 100644
index 0000000000..66a0a9e0c7
--- /dev/null
+++ b/usr/src/cmd/auditd/auditd.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0"?>
+<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
+<!--
+ Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ Use is subject to license terms.
+
+ CDDL HEADER START
+
+ The contents of this file are subject to the terms of the
+ Common Development and Distribution License, Version 1.0 only
+ (the "License"). You may not use this file except in compliance
+ with the License.
+
+ You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ or http://www.opensolaris.org/os/licensing.
+ See the License for the specific language governing permissions
+ and limitations under the License.
+
+ When distributing Covered Code, include this CDDL HEADER in each
+ file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ If applicable, add the following below this CDDL HEADER, with the
+ fields enclosed by brackets "[]" replaced with your own identifying
+ information: Portions Copyright [yyyy] [name of copyright owner]
+
+ CDDL HEADER END
+
+ ident "%Z%%M% %I% %E% SMI"
+
+ NOTE: This service manifest is not editable; its contents will
+ be overwritten by package or patch operations, including
+ operating system upgrade. Make customizations in a different
+ file.
+-->
+
+<service_bundle type='manifest' name='SUNWcsr:auditd'>
+
+<service
+ name='system/auditd'
+ type='service'
+ version='1'>
+
+ <single_instance />
+
+ <dependency
+ name='usr'
+ type='service'
+ grouping='require_all'
+ restart_on='none'>
+ <service_fmri value='svc:/system/filesystem/local' />
+ </dependency>
+
+ <dependency
+ name='ns'
+ type='service'
+ grouping='require_all'
+ restart_on='none'>
+ <service_fmri value='svc:/milestone/name-services' />
+ </dependency>
+
+ <dependency
+ name='syslog'
+ type='service'
+ grouping='require_all'
+ restart_on='none'>
+ <service_fmri value='svc:/system/system-log' />
+ </dependency>
+
+ <dependent
+ name='auditd_multi-user'
+ grouping='optional_all'
+ restart_on='none'>
+ <service_fmri value='svc:/milestone/multi-user'/>
+ </dependent>
+
+ <dependent
+ name='console-login'
+ grouping='optional_all'
+ restart_on='none'>
+ <service_fmri value='svc:/system/console-login'/>
+ </dependent>
+
+ <exec_method
+ type='method'
+ name='start'
+ exec='/lib/svc/method/svc-auditd'
+ timeout_seconds='15'>
+ <method_context>
+ <method_credential user='root' group='root' />
+ </method_context>
+ </exec_method>
+
+ <exec_method
+ type='method'
+ name='refresh'
+ exec=':kill -HUP'
+ timeout_seconds='30'>
+ <method_context>
+ <method_credential user='root' group='root' />
+ </method_context>
+ </exec_method>
+
+ <!--
+ auditd waits for c2audit to quiet down after catching a
+ -TERM before exiting; auditd's timeout is 20 seconds
+ -->
+
+ <exec_method
+ type='method'
+ name='stop'
+ exec=':kill -TERM'
+ timeout_seconds='30'>
+ <method_context>
+ <method_credential user='root' group='root' />
+ </method_context>
+ </exec_method>
+
+ <!-- SIGs HUP, TERM, and USR1 are all expected by auditd -->
+
+ <property_group name='startd' type='framework'>
+ <propval name='ignore_error' type='astring'
+ value='core,signal' />
+ </property_group>
+
+ <property_group name='general' type='framework'>
+ <!-- to start stop auditd -->
+ <propval name='action_authorization' type='astring'
+ value='solaris.audit.config' />
+ </property_group>
+
+ <instance name='default' enabled='false' />
+
+ <stability value='Evolving' />
+
+ <template>
+ <common_name>
+ <loctext xml:lang='C'>
+ Solaris audit daemon
+ </loctext>
+ </common_name>
+ <documentation>
+ <manpage title='auditd'
+ section='1M'
+ manpath='/usr/share/man'/>
+ <manpage title='audit'
+ section='1M'
+ manpath='/usr/share/man'/>
+ </documentation>
+ </template>
+
+</service>
+
+</service_bundle>
diff --git a/usr/src/cmd/auditd/doorway.c b/usr/src/cmd/auditd/doorway.c
new file mode 100644
index 0000000000..3a1a6756bb
--- /dev/null
+++ b/usr/src/cmd/auditd/doorway.c
@@ -0,0 +1,1317 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ */
+#pragma ident "%Z%%M% %I% %E% SMI"
+/*
+ * Threads:
+ *
+ * auditd is thread 0 and does signal handling
+ *
+ * input() is a door server that receives binary audit records and
+ * queues them for handling by an instance of process() for conversion to syslog
+ * message(s). There is one process thread per plugin.
+ *
+ * Queues:
+ *
+ * Each plugin has a buffer pool and and queue for feeding the
+ * the process threads. The input thread moves buffers from the pool
+ * to the queue and the process thread puts them back.
+ *
+ * Another pool, b_pool, contains buffers referenced by each of the
+ * process queues; this is to minimize the number of buffer copies
+ *
+ */
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <bsm/adt.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libintl.h>
+#include <pthread.h>
+#include <secdb.h>
+#include <security/auditd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <audit_plugin.h> /* libbsm */
+#include "plugin.h"
+#include <bsm/audit_door_infc.h>
+#include "audit_sig_infc.h"
+#include "queue.h"
+
+#define DEBUG 0
+
+#if DEBUG
+
+static FILE *dbfp;
+#define DUMP(w, x, y, z) dump_state(w, x, y, z)
+#define DPRINT(x) {(void) fprintf x; }
+
+#else
+
+#define DUMP(w, x, y, z)
+#define DPRINT(x)
+
+#endif
+
+#define FATAL_MESSAGE_LEN 256
+
+#define MIN_RECORD_SIZE (size_t)25
+
+#define INPUT_MIN 2
+#define THRESHOLD_PCT 75
+#define DEFAULT_BUF_SZ (size_t)250
+#define BASE_PRIORITY 10 /* 0 - 20 valid for user, time share */
+#define HIGH_PRIORITY BASE_PRIORITY - 1
+
+static thr_data_t in_thr; /* input thread locks and data */
+static int doorfd = -1;
+
+static int largest_queue = INPUT_MIN;
+static au_queue_t b_pool;
+static int b_allocated = 0;
+static pthread_mutex_t b_alloc_lock;
+static pthread_mutex_t b_refcnt_lock;
+
+static void input(void *, void *, int, door_desc_t *, int);
+static void process(plugin_t *);
+
+static audit_q_t *qpool_withdraw(plugin_t *);
+static void qpool_init(plugin_t *, int);
+static void qpool_return(plugin_t *, audit_q_t *);
+static void qpool_close(plugin_t *);
+
+static audit_rec_t *bpool_withdraw(char *, size_t, size_t);
+static void bpool_init();
+static void bpool_return(audit_rec_t *);
+
+/*
+ * warn_or_fatal() -- log daemon error and (optionally) exit
+ */
+static void
+warn_or_fatal(int fatal, char *parting_shot)
+{
+ char *severity;
+ char message[512];
+
+ if (fatal)
+ severity = gettext("fatal error");
+ else
+ severity = gettext("warning");
+
+ (void) snprintf(message, 512, "%s: %s", severity, parting_shot);
+
+ __audit_syslog("auditd", LOG_PID | LOG_ODELAY | LOG_CONS,
+ LOG_DAEMON, LOG_ALERT, message);
+
+ DPRINT((dbfp, "auditd warn_or_fatal %s: %s\n", severity, parting_shot));
+ if (fatal)
+ auditd_exit(1);
+}
+
+/* Internal to doorway.c errors... */
+#define INTERNAL_LOAD_ERROR -1
+#define INTERNAL_SYS_ERROR -2
+#define INTERNAL_CONFIG_ERROR -3
+
+/*
+ * report_error -- handle errors returned by plugin
+ *
+ * rc is plugin's return code if it is a non-negative value,
+ * otherwise it is a doorway.c code about a plugin.
+ */
+static void
+report_error(auditd_rc_t rc, char *error_text, char *plugin_path)
+{
+ int warn = 0;
+ char rcbuf[100]; /* short error name string */
+ char message[FATAL_MESSAGE_LEN];
+ int bad_count = 0;
+ char *name;
+ char empty[] = "..";
+
+ static int no_plug = 0;
+ static int no_load = 0;
+ static int no_thread;
+ static int no_memory = 0;
+ static int invalid = 0;
+ static int retry = 0;
+ static int fail = 0;
+
+ name = plugin_path;
+ if (error_text == NULL)
+ error_text = empty;
+ if (name == NULL)
+ name = empty;
+
+ switch (rc) {
+ case INTERNAL_LOAD_ERROR:
+ warn = 1;
+ bad_count = ++no_load;
+ (void) strcpy(rcbuf, "load_error");
+ break;
+ case INTERNAL_SYS_ERROR:
+ warn = 1;
+ bad_count = ++no_thread;
+ (void) strcpy(rcbuf, "sys_error");
+ break;
+ case INTERNAL_CONFIG_ERROR:
+ warn = 1;
+ bad_count = ++no_plug;
+ (void) strcpy(rcbuf, "config_error");
+ name = strdup("--");
+ break;
+ case AUDITD_SUCCESS:
+ break;
+ case AUDITD_NO_MEMORY: /* no_memory */
+ warn = 1;
+ bad_count = ++no_memory;
+ (void) strcpy(rcbuf, "no_memory");
+ break;
+ case AUDITD_INVALID: /* invalid */
+ warn = 1;
+ bad_count = ++invalid;
+ (void) strcpy(rcbuf, "invalid");
+ break;
+ case AUDITD_RETRY:
+ warn = 1;
+ bad_count = ++retry;
+ (void) strcpy(rcbuf, "retry");
+ break;
+ case AUDITD_COMM_FAIL: /* comm_fail */
+ (void) strcpy(rcbuf, "comm_fail");
+ break;
+ case AUDITD_FATAL: /* failure */
+ warn = 1;
+ bad_count = ++fail;
+ (void) strcpy(rcbuf, "failure");
+ break;
+ default:
+ (void) strcpy(rcbuf, "error");
+ break;
+ }
+ DPRINT((dbfp, "report_error(%d - %s): %s\n\t%s\n",
+ bad_count, name, rcbuf, error_text));
+ if (warn)
+ __audit_dowarn2("plugin", name, rcbuf, error_text, bad_count);
+ else {
+ (void) snprintf(message, FATAL_MESSAGE_LEN,
+ gettext("audit plugin %s reported error = \"%s\": %s\n"),
+ name, rcbuf, error_text);
+ warn_or_fatal(0, message);
+ }
+}
+
+static size_t
+getlen(char *buf)
+{
+ adr_t adr;
+ char tokenid;
+ uint32_t len;
+
+ adr.adr_now = buf;
+ adr.adr_stream = buf;
+
+ adrm_char(&adr, &tokenid, 1);
+ if ((tokenid == AUT_OHEADER) || (tokenid == AUT_HEADER32) ||
+ (tokenid == AUT_HEADER32_EX) || (tokenid == AUT_HEADER64) ||
+ (tokenid == AUT_HEADER64_EX)) {
+ adrm_u_int32(&adr, &len, 1);
+
+ return (len);
+ }
+ DPRINT((dbfp, "getlen() is not looking at a header token\n"));
+
+ return (0);
+}
+
+/*
+ * load_function - call dlsym() to resolve the function address
+ */
+static int
+load_function(plugin_t *p, char *name, auditd_rc_t (**func)())
+{
+ *func = (auditd_rc_t (*)())dlsym(p->plg_dlptr, name);
+ if (*func == NULL) {
+ char message[FATAL_MESSAGE_LEN];
+ char *errmsg = dlerror();
+
+ (void) snprintf(message, FATAL_MESSAGE_LEN,
+ gettext("dlsym failed %s: error %s"),
+ name, errmsg != NULL ? errmsg : gettext("Unknown error\n"));
+
+ warn_or_fatal(0, message);
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * load the auditd plug in
+ */
+static int
+load_plugin(plugin_t *p)
+{
+ struct stat64 stat;
+ int fd;
+ int fail = 0;
+
+ /*
+ * Stat the file so we can check modes and ownerships
+ */
+ if ((fd = open(p->plg_path, O_NONBLOCK | O_RDONLY)) != -1) {
+ if ((fstat64(fd, &stat) == -1) || (!S_ISREG(stat.st_mode)))
+ fail = 1;
+ } else
+ fail = 1;
+ if (fail) {
+ char message[FATAL_MESSAGE_LEN];
+
+ (void) snprintf(message, FATAL_MESSAGE_LEN,
+ gettext("auditd plugin: stat(%s) failed: %s\n"),
+ p->plg_path, strerror(errno));
+
+ warn_or_fatal(0, message);
+ return (-1);
+ }
+ /*
+ * Check the ownership of the file
+ */
+ if (stat.st_uid != (uid_t)0) {
+ char message[FATAL_MESSAGE_LEN];
+
+ (void) snprintf(message, FATAL_MESSAGE_LEN,
+ gettext(
+ "auditd plugin: Owner of the module %s is not root\n"),
+ p->plg_path);
+
+ warn_or_fatal(0, message);
+ return (-1);
+ }
+ /*
+ * Check the modes on the file
+ */
+ if (stat.st_mode&S_IWGRP) {
+ char message[FATAL_MESSAGE_LEN];
+
+ (void) snprintf(message, FATAL_MESSAGE_LEN,
+ gettext("auditd plugin: module %s writable by group\n"),
+ p->plg_path);
+
+ warn_or_fatal(0, message);
+ return (-1);
+ }
+ if (stat.st_mode&S_IWOTH) {
+ char message[FATAL_MESSAGE_LEN];
+
+ (void) snprintf(message, FATAL_MESSAGE_LEN,
+ gettext("auditd plugin: module %s writable by world\n"),
+ p->plg_path);
+
+ warn_or_fatal(0, message);
+ return (-1);
+ }
+ /*
+ * Open the plugin
+ */
+ p->plg_dlptr = dlopen(p->plg_path, RTLD_LAZY);
+
+ if (p->plg_dlptr == NULL) {
+ char message[FATAL_MESSAGE_LEN];
+ char *errmsg = dlerror();
+
+ (void) snprintf(message, FATAL_MESSAGE_LEN,
+ gettext("plugin load %s failed: %s\n"),
+ p->plg_path, errmsg != NULL ? errmsg :
+ gettext("Unknown error\n"));
+
+ warn_or_fatal(0, message);
+ return (-1);
+ }
+ if (load_function(p, "auditd_plugin", &(p->plg_fplugin)))
+ return (-1);
+
+ if (load_function(p, "auditd_plugin_open", &(p->plg_fplugin_open)))
+ return (-1);
+
+ if (load_function(p, "auditd_plugin_close", &(p->plg_fplugin_close)))
+ return (-1);
+
+ return (0);
+}
+
+/*
+ * unload_plugin() unlinks and frees the plugin_t structure after
+ * freeing buffers and structures that hang off it. It also dlcloses
+ * the referenced plugin. The return is the next entry, which may be NULL
+ *
+ * hold plugin_mutex for this call
+ */
+static plugin_t *
+unload_plugin(plugin_t *p)
+{
+ plugin_t *q, **r;
+
+ assert(pthread_mutex_trylock(&plugin_mutex) != 0);
+
+ DPRINT((dbfp, "unload_plugin: removing %s\n", p->plg_path));
+
+ _kva_free(p->plg_kvlist); /* _kva_free accepts NULL */
+ qpool_close(p); /* qpool_close accepts NULL pool, queue */
+ DPRINT((dbfp, "unload_plugin: %s structure removed\n", p->plg_path));
+
+ (void) dlclose(p->plg_dlptr);
+
+ DPRINT((dbfp, "unload_plugin: %s dlclosed\n", p->plg_path));
+ free(p->plg_path);
+
+ (void) pthread_mutex_destroy(&(p->plg_mutex));
+ (void) pthread_cond_destroy(&(p->plg_cv));
+
+ q = plugin_head;
+ r = &plugin_head;
+ while (q != NULL) {
+ if (q == p) {
+ *r = p->plg_next;
+ free(p);
+ break;
+ }
+ r = &(q->plg_next);
+ q = q->plg_next;
+ }
+ return (*r);
+}
+
+/*
+ * process return values from plugin_open
+ *
+ * presently no attribute is defined.
+ */
+/* ARGSUSED */
+static void
+open_return(plugin_t *p, char *attrval)
+{
+}
+
+/*
+ * auditd_thread_init
+ * - create threads
+ * - load plugins
+ *
+ * auditd_thread_init is called at auditd startup with an initial list
+ * of plugins and again each time audit catches a AU_SIG_READ_CONTROL
+ * or AU_SIG_NEXT_DIR.
+ *
+ */
+int
+auditd_thread_init()
+{
+ int threshold;
+ auditd_rc_t rc;
+ plugin_t *p;
+ char *open_params;
+ char *error_string;
+ int plugin_count = 0;
+ static int threads_ready = 0;
+
+ if (!threads_ready) {
+ struct sched_param param;
+#if DEBUG
+ dbfp = __auditd_debug_file_open();
+#endif
+ doorfd = door_create((void(*)())input, 0,
+ DOOR_REFUSE_DESC | DOOR_NO_CANCEL);
+ if (doorfd < 0)
+ return (1); /* can't create door -> fatal */
+
+ param.sched_priority = BASE_PRIORITY;
+ (void) pthread_setschedparam(pthread_self(), SCHED_OTHER,
+ &param);
+
+ /* input door server */
+ (void) pthread_mutex_init(&(in_thr.thd_mutex), NULL);
+ (void) pthread_cond_init(&(in_thr.thd_cv), NULL);
+ in_thr.thd_waiting = 0;
+
+ bpool_init();
+ }
+ p = plugin_head;
+ while (p != NULL) {
+ if (p->plg_removed) {
+ DPRINT((dbfp, "start removing %s\n", p->plg_path));
+ /* tell process(p) to exit and dlclose */
+ (void) pthread_cond_signal(&(p->plg_cv));
+ } else if (!p->plg_initialized) {
+ DPRINT((dbfp, "start initial load of %s\n",
+ p->plg_path));
+ if (load_plugin(p)) {
+ report_error(INTERNAL_LOAD_ERROR,
+ gettext("dynamic load failed"),
+ p->plg_path);
+ p = unload_plugin(p);
+ continue;
+ }
+ open_params = NULL;
+ error_string = NULL;
+ if ((rc = p->plg_fplugin_open(
+ p->plg_kvlist,
+ &open_params, &error_string)) != AUDITD_SUCCESS) {
+ report_error(rc, error_string, p->plg_path);
+ free(error_string);
+ p = unload_plugin(p);
+ continue;
+ }
+ open_return(p, open_params);
+ p->plg_reopen = 0;
+
+ threshold = ((p->plg_qmax * THRESHOLD_PCT) + 99) / 100;
+ p->plg_qmin = INPUT_MIN;
+
+ DPRINT((dbfp,
+ "calling qpool_init for %s with qmax=%d\n",
+ p->plg_path, p->plg_qmax));
+
+ qpool_init(p, threshold);
+ audit_queue_init(&(p->plg_queue));
+ p->plg_initialized = 1;
+
+ (void) pthread_mutex_init(&(p->plg_mutex), NULL);
+ (void) pthread_cond_init(&(p->plg_cv), NULL);
+ p->plg_waiting = 0;
+
+ if (pthread_create(&(p->plg_tid), NULL,
+ (void *(*)(void *))process, p)) {
+ report_error(INTERNAL_SYS_ERROR,
+ gettext("thread creation failed"),
+ p->plg_path);
+ p = unload_plugin(p);
+ continue;
+ }
+ } else if (p->plg_reopen) {
+ DPRINT((dbfp, "reopen %s\n", p->plg_path));
+ error_string = NULL;
+ if ((rc = p->plg_fplugin_open(
+ p->plg_kvlist,
+ &open_params, &error_string)) != AUDITD_SUCCESS) {
+
+ report_error(rc, error_string, p->plg_path);
+ free(error_string);
+ p = unload_plugin(p);
+ continue;
+ }
+ open_return(p, open_params);
+ p->plg_reopen = 0;
+
+ DPRINT((dbfp, "%s qmax=%d\n",
+ p->plg_path, p->plg_qmax));
+
+ }
+ p->plg_q_threshold = ((p->plg_qmax * THRESHOLD_PCT) + 99) / 100;
+
+ p = p->plg_next;
+ plugin_count++;
+ }
+ if (plugin_count == 0) {
+ report_error(INTERNAL_CONFIG_ERROR,
+ gettext("No plugins are configured"), NULL);
+ return (-1);
+ }
+ if (!threads_ready) {
+ /* unleash the kernel */
+ rc = auditdoor(doorfd);
+
+ DPRINT((dbfp, "%d returned from auditdoor.\n",
+ rc));
+ if (rc != 0)
+ return (1); /* fatal */
+
+ threads_ready = 1;
+ }
+ return (0);
+}
+
+/*
+ * Door invocations that are in progress during a
+ * door_revoke() invocation are allowed to complete normally.
+ * -- man page for door_revoke()
+ */
+void
+auditd_thread_close()
+{
+ if (doorfd == -1)
+ return;
+ (void) door_revoke(doorfd);
+ doorfd = -1;
+}
+
+/*
+ * qpool_init() sets up pool for queue entries (audit_q_t)
+ *
+ */
+static void
+qpool_init(plugin_t *p, int threshold)
+{
+ int i;
+ audit_q_t *node;
+
+ audit_queue_init(&(p->plg_pool));
+
+ DPRINT((dbfp, "qpool_init(%d) max, min, threshhold = %d, %d, %d\n",
+ p->plg_tid, p->plg_qmax, p->plg_qmin, threshold));
+
+ if (p->plg_qmax > largest_queue)
+ largest_queue = p->plg_qmax;
+
+ p->plg_q_threshold = threshold;
+
+ for (i = 0; i < p->plg_qmin; i++) {
+ node = malloc(sizeof (audit_q_t));
+ if (node == NULL)
+ warn_or_fatal(1, gettext("no memory\n"));
+ /* doesn't return */
+
+ audit_enqueue(&p->plg_pool, node);
+ }
+}
+
+/*
+ * bpool_init() sets up pool and queue for record entries (audit_rec_t)
+ *
+ */
+static void
+bpool_init()
+{
+ int i;
+ audit_rec_t *node;
+
+ audit_queue_init(&b_pool);
+ (void) pthread_mutex_init(&b_alloc_lock, NULL);
+ (void) pthread_mutex_init(&b_refcnt_lock, NULL);
+
+ for (i = 0; i < INPUT_MIN; i++) {
+ node = malloc(AUDIT_REC_HEADER + DEFAULT_BUF_SZ);
+ if (node == NULL)
+ warn_or_fatal(1, gettext("no memory\n"));
+ /* doesn't return */
+
+ node->abq_buf_len = DEFAULT_BUF_SZ;
+
+ node->abq_data_len = 0;
+ audit_enqueue(&b_pool, node);
+ (void) pthread_mutex_lock(&b_alloc_lock);
+ b_allocated++;
+ (void) pthread_mutex_unlock(&b_alloc_lock);
+ }
+}
+
+/*
+ * qpool_close() discard queue and pool for a discontinued plugin
+ *
+ * there is no corresponding bpool_close() since it would only
+ * be called as auditd is going down.
+ */
+static void
+qpool_close(plugin_t *p) {
+ audit_q_t *q_node;
+ audit_rec_t *b_node;
+
+ if (!p->plg_initialized)
+ return;
+
+ while (audit_dequeue(&(p->plg_pool), (void *)&q_node) == 0) {
+ free(q_node);
+ }
+ audit_queue_destroy(&(p->plg_pool));
+
+ while (audit_dequeue(&(p->plg_queue), (void *)&q_node) == 0) {
+ b_node = audit_release(&b_refcnt_lock, q_node->aqq_data);
+ if (b_node != NULL)
+ audit_enqueue(&b_pool, b_node);
+ free(q_node);
+ }
+ audit_queue_destroy(&(p->plg_queue));
+}
+
+/*
+ * qpool_withdraw
+ */
+static audit_q_t *
+qpool_withdraw(plugin_t *p)
+{
+ audit_q_t *node;
+ int rc;
+
+ /* get a buffer from the pool, if any */
+ rc = audit_dequeue(&(p->plg_pool), (void *)&node);
+ if (rc == 0)
+ return (node);
+
+ /*
+ * the pool is empty: allocate a new element
+ */
+ node = malloc(sizeof (audit_q_t));
+
+ if (node == NULL)
+ warn_or_fatal(1, gettext("no memory\n"));
+ /* doesn't return */
+
+ return (node);
+}
+
+/*
+ * bpool_withdraw -- gets a buffer and fills it
+ *
+ */
+static audit_rec_t *
+bpool_withdraw(char *buffer, size_t buff_size, size_t request_size)
+{
+ audit_rec_t *node;
+ int rc;
+ size_t new_length;
+
+ new_length = (request_size > DEFAULT_BUF_SZ) ?
+ request_size : DEFAULT_BUF_SZ;
+
+ /* get a buffer from the pool, if any */
+ rc = audit_dequeue(&b_pool, (void *)&node);
+
+ DPRINT((dbfp, "bpool_withdraw buf length=%d,"
+ " requested size=%d, dequeue rc=%d\n",
+ new_length, request_size, rc));
+
+ if (rc == 0) {
+ DPRINT((dbfp, "bpool_withdraw node=%X (pool=%d)\n", node,
+ audit_queue_size(&b_pool)));
+
+ if (new_length > node->abq_buf_len) {
+ node = realloc(node, AUDIT_REC_HEADER + new_length);
+ if (node == NULL)
+ warn_or_fatal(1, gettext("no memory\n"));
+ /* no return */
+ }
+ } else {
+ /*
+ * the pool is empty: allocate a new element
+ */
+ (void) pthread_mutex_lock(&b_alloc_lock);
+ if (b_allocated >= largest_queue) {
+ (void) pthread_mutex_unlock(&b_alloc_lock);
+ DPRINT((dbfp, "bpool_withdraw is over max (pool=%d)\n",
+ audit_queue_size(&b_pool)));
+ return (NULL);
+ }
+ (void) pthread_mutex_unlock(&b_alloc_lock);
+
+ node = malloc(AUDIT_REC_HEADER + new_length);
+
+ if (node == NULL)
+ warn_or_fatal(1, gettext("no memory\n"));
+ /* no return */
+
+ (void) pthread_mutex_lock(&b_alloc_lock);
+ b_allocated++;
+ (void) pthread_mutex_unlock(&b_alloc_lock);
+ DPRINT((dbfp, "bpool_withdraw node=%X (alloc=%d, pool=%d)\n",
+ node, b_allocated, audit_queue_size(&b_pool)));
+ }
+ assert(request_size <= new_length);
+
+ (void) memcpy(node->abq_buffer, buffer, buff_size);
+ node->abq_data_len = buff_size;
+ node->abq_buf_len = new_length;
+ node->abq_ref_count = 0;
+
+ return (node);
+}
+
+/*
+ * qpool_return() moves queue nodes back to the pool queue.
+ *
+ * if the pool is over max, the node is discarded instead.
+ */
+static void
+qpool_return(plugin_t *p, audit_q_t *node)
+{
+ int qpool_size;
+ int q_size;
+
+#if DEBUG
+ uint32_t sequence = node->aqq_sequence;
+#endif
+ qpool_size = audit_queue_size(&(p->plg_pool));
+ q_size = audit_queue_size(&(p->plg_queue));
+
+ if (qpool_size + q_size > p->plg_qmax)
+ free(node);
+ else
+ audit_enqueue(&(p->plg_pool), node);
+
+ DPRINT((dbfp,
+ "qpool_return(%d): seq=%d, q size=%d,"
+ " pool size=%d (total alloc=%d), threshhold=%d\n",
+ p->plg_tid, sequence, q_size, qpool_size,
+ q_size + qpool_size, p->plg_q_threshold));
+}
+
+/*
+ * bpool_return() moves queue nodes back to the pool queue.
+ */
+static void
+bpool_return(audit_rec_t *node)
+{
+#if DEBUG
+ audit_rec_t *copy = node;
+#endif
+ node = audit_release(&b_refcnt_lock, node); /* decrement ref cnt */
+
+ if (node != NULL) { /* NULL if ref cnt is not zero */
+ audit_enqueue(&b_pool, node);
+ DPRINT((dbfp,
+ "bpool_return: requeue %X (allocated=%d,"
+ " pool size=%d)\n", node, b_allocated,
+ audit_queue_size(&b_pool)));
+ }
+#if DEBUG
+ else {
+ DPRINT((dbfp,
+ "bpool_return: decrement count for %X (allocated=%d,"
+ " pool size=%d)\n", copy, b_allocated,
+ audit_queue_size(&b_pool)));
+ }
+#endif
+}
+
+#if DEBUG
+static void
+dump_state(char *src, plugin_t *p, int count, char *msg)
+{
+ struct sched_param param;
+ int policy;
+/*
+ * count is message sequence
+ */
+ (void) pthread_getschedparam(p->plg_tid, &policy, &param);
+ (void) fprintf(dbfp, "%7s(%d/%d) %11s:"
+ " input_in_wait=%d"
+ " priority=%d"
+ " queue size=%d pool size=%d"
+ "\n\t"
+ "process wait=%d"
+ " tossed=%d"
+ " queued=%d"
+ " written=%d"
+ "\n",
+ src, p->plg_tid, count, msg,
+ in_thr.thd_waiting, param.sched_priority,
+ audit_queue_size(&(p->plg_queue)),
+ audit_queue_size(&(p->plg_pool)),
+ p->plg_waiting, p->plg_tossed,
+ p->plg_queued, p->plg_output);
+
+ (void) fflush(dbfp);
+}
+#endif
+
+/*
+ * policy_is_block: return 1 if the continue policy is off for any active
+ * plugin, else 0
+ */
+static int
+policy_is_block()
+{
+ plugin_t *p;
+
+ (void) pthread_mutex_lock(&plugin_mutex);
+ p = plugin_head;
+
+ while (p != NULL) {
+ if (p->plg_cnt == 0) {
+ (void) pthread_mutex_unlock(&plugin_mutex);
+ DPRINT((dbfp,
+ "policy_is_block: policy is to block\n"));
+ return (1);
+ }
+ p = p->plg_next;
+ }
+ (void) pthread_mutex_unlock(&plugin_mutex);
+ DPRINT((dbfp, "policy_is_block: policy is to continue\n"));
+ return (0);
+}
+
+/*
+ * policy_update() -- the kernel has received a policy change.
+ * Presently, the only policy auditd cares about is AUDIT_CNT
+ */
+static void
+policy_update(uint32_t newpolicy)
+{
+ plugin_t *p;
+
+ DPRINT((dbfp, "policy change: %X\n", newpolicy));
+ (void) pthread_mutex_lock(&plugin_mutex);
+ p = plugin_head;
+ while (p != NULL) {
+ p->plg_cnt = (newpolicy & AUDIT_CNT) ? 1 : 0;
+ (void) pthread_cond_signal(&(p->plg_cv));
+
+ DPRINT((dbfp, "policy changed for thread %d\n", p->plg_tid));
+ p = p->plg_next;
+ }
+ (void) pthread_mutex_unlock(&plugin_mutex);
+}
+
+/*
+ * queue_buffer() inputs a buffer and queues for each active plugin if
+ * it represents a complete audit record. Otherwise it builds a
+ * larger buffer to hold the record and take successive buffers from
+ * c2audit to build a complete record; then queues it for each plugin.
+ *
+ * return 0 if data is queued (or damaged and tossed). If resources
+ * are not available, return 0 if all active plugins have the cnt
+ * policy set, else 1. 0 is also returned if the input is a control
+ * message. (aub_buf is aligned on a 64 bit boundary, so casting
+ * it to an integer works just fine.)
+ */
+static int
+queue_buffer(au_dbuf_t *kl)
+{
+ plugin_t *p;
+ audit_rec_t *b_copy;
+ audit_q_t *q_copy;
+ boolean_t referenced = 0;
+ static char *invalid_msg = "invalid audit record discarded";
+ static char *invalid_control =
+ "invalid audit control discarded";
+
+ static audit_rec_t *alt_b_copy = NULL;
+ static size_t alt_length;
+ static size_t alt_offset;
+
+ /*
+ * the buffer may be a kernel -> auditd message. (only
+ * the policy change message exists so far.)
+ */
+
+ if ((kl->aub_type & AU_DBUF_NOTIFY) != 0) {
+ uint32_t control;
+
+ control = kl->aub_type & ~AU_DBUF_NOTIFY;
+ switch (control) {
+ case AU_DBUF_POLICY:
+ /* LINTED */
+ policy_update(*(uint32_t *)kl->aub_buf);
+ break;
+ case AU_DBUF_SHUTDOWN:
+ (void) kill(getpid(), AU_SIG_DISABLE);
+ DPRINT((dbfp, "AU_DBUF_SHUTDOWN message\n"));
+ break;
+ default:
+ warn_or_fatal(0, gettext(invalid_control));
+ break;
+ }
+ return (0);
+ }
+ /*
+ * The test for valid continuation/completion may fail. Need to
+ * assume the failure was earlier and that this buffer may
+ * be a valid first or complete buffer after discarding the
+ * incomplete record
+ */
+
+ if (alt_b_copy != NULL) {
+ if ((kl->aub_type == AU_DBUF_FIRST) ||
+ (kl->aub_type == AU_DBUF_COMPLETE)) {
+ DPRINT((dbfp, "copy is not null, partial is %d\n",
+ kl->aub_type));
+ bpool_return(alt_b_copy);
+ warn_or_fatal(0, gettext(invalid_msg));
+ alt_b_copy = NULL;
+ }
+ }
+ if (alt_b_copy != NULL) { /* continue collecting a long record */
+ if (kl->aub_size + alt_offset > alt_length) {
+ bpool_return(alt_b_copy);
+ alt_b_copy = NULL;
+ warn_or_fatal(0, gettext(invalid_msg));
+ return (0);
+ }
+ (void) memcpy(alt_b_copy->abq_buffer + alt_offset, kl->aub_buf,
+ kl->aub_size);
+ alt_offset += kl->aub_size;
+ if (kl->aub_type == AU_DBUF_MIDDLE)
+ return (0);
+ b_copy = alt_b_copy;
+ alt_b_copy = NULL;
+ b_copy->abq_data_len = alt_length;
+ } else if (kl->aub_type == AU_DBUF_FIRST) {
+ /* first buffer of a multiple buffer record */
+ alt_length = getlen(kl->aub_buf);
+ if ((alt_length < MIN_RECORD_SIZE) ||
+ (alt_length <= kl->aub_size)) {
+ warn_or_fatal(0, gettext(invalid_msg));
+ return (0);
+ }
+ alt_b_copy = bpool_withdraw(kl->aub_buf, kl->aub_size,
+ alt_length);
+
+ if (alt_b_copy == NULL)
+ return (policy_is_block());
+
+ alt_offset = kl->aub_size;
+ return (0);
+ } else { /* one buffer, one record -- the basic case */
+ if (kl->aub_type != AU_DBUF_COMPLETE) {
+ DPRINT((dbfp, "copy is null, partial is %d\n",
+ kl->aub_type));
+ warn_or_fatal(0, gettext(invalid_msg));
+ return (0); /* tossed */
+ }
+ b_copy = bpool_withdraw(kl->aub_buf, kl->aub_size,
+ kl->aub_size);
+
+ if (b_copy == NULL)
+ return (policy_is_block());
+ }
+
+ (void) pthread_mutex_lock(&plugin_mutex);
+ p = plugin_head;
+ while (p != NULL) {
+ if (!p->plg_removed) {
+ /*
+ * Link the record buffer to the input queues.
+ * To avoid a race, it is necessary to wait
+ * until all reference count increments
+ * are complete before queueing q_copy.
+ */
+ audit_incr_ref(&b_refcnt_lock, b_copy);
+
+ q_copy = qpool_withdraw(p);
+ q_copy->aqq_sequence = p->plg_sequence++;
+ q_copy->aqq_data = b_copy;
+
+ p->plg_save_q_copy = q_copy; /* enqueue below */
+ referenced = 1;
+ } else
+ p->plg_save_q_copy = NULL;
+ p = p->plg_next;
+ }
+ /*
+ * now that the reference count is updated, queue it.
+ */
+ if (referenced) {
+ p = plugin_head;
+ while ((p != NULL) && (p->plg_save_q_copy != NULL)) {
+ audit_enqueue(&(p->plg_queue), p->plg_save_q_copy);
+ (void) pthread_cond_signal(&(p->plg_cv));
+ p->plg_queued++;
+ p = p->plg_next;
+ }
+ } else
+ bpool_return(b_copy);
+
+ (void) pthread_mutex_unlock(&plugin_mutex);
+
+ return (0);
+}
+
+/*
+ * wait_a_while() -- timed wait in the door server to allow output
+ * time to catch up.
+ */
+static void
+wait_a_while() {
+ struct timespec delay = {0, 500000000}; /* 1/2 second */;
+
+ (void) pthread_mutex_lock(&(in_thr.thd_mutex));
+ in_thr.thd_waiting = 1;
+ (void) pthread_cond_reltimedwait_np(&(in_thr.thd_cv),
+ &(in_thr.thd_mutex), &delay);
+ in_thr.thd_waiting = 0;
+ (void) pthread_mutex_unlock(&(in_thr.thd_mutex));
+}
+
+/*
+ * adjust_priority() -- check queue and pools and adjust the priority
+ * for process() accordingly. If we're way ahead of output, do a
+ * timed wait as well.
+ */
+static void
+adjust_priority() {
+ int queue_near_full;
+ plugin_t *p;
+ int queue_size;
+ struct sched_param param;
+
+ queue_near_full = 0;
+ (void) pthread_mutex_lock(&plugin_mutex);
+ p = plugin_head;
+ while (p != NULL) {
+ queue_size = audit_queue_size(&(p->plg_queue));
+ if (queue_size > p->plg_q_threshold) {
+ if (p->plg_priority != HIGH_PRIORITY) {
+ p->plg_priority =
+ param.sched_priority =
+ HIGH_PRIORITY;
+ (void) pthread_setschedparam(p->plg_tid,
+ SCHED_OTHER, &param);
+ }
+ if (queue_size > p->plg_qmax - p->plg_qmin) {
+ queue_near_full = 1;
+ break;
+ }
+ }
+ p = p->plg_next;
+ }
+ (void) pthread_mutex_unlock(&plugin_mutex);
+
+ if (queue_near_full) {
+ DPRINT((dbfp,
+ "adjust_priority: input taking a short break\n"));
+ wait_a_while();
+ DPRINT((dbfp,
+ "adjust_priority: input back from my break\n"));
+ }
+}
+
+/*
+ * input() is a door server; it blocks if any plugins have full queues
+ * with the continue policy off. (auditconfig -policy -cnt)
+ *
+ * input() is called synchronously from c2audit and is NOT
+ * reentrant due to the (unprotected) static variables in
+ * queue_buffer(). If multiple clients are created, a context
+ * structure will be required for queue_buffer.
+ *
+ * timedwait is used when input() gets too far ahead of process();
+ * the wait terminates either when the set time expires or when
+ * process() signals that it has nearly caught up.
+ */
+/* ARGSUSED */
+static void
+input(void *cookie, void *argp, int arg_size, door_desc_t *dp,
+ int n_descriptors)
+{
+ int is_blocked;
+ plugin_t *p;
+#if DEBUG
+ int loop_count = 0;
+ static int call_counter = 0;
+#endif
+ if (argp == NULL) {
+ warn_or_fatal(0,
+ gettext("invalid data received from c2audit\n"));
+ goto input_exit;
+ }
+ DPRINT((dbfp, "%d input new buffer: length=%u, "
+ "partial=%u, arg_size=%d\n",
+ ++call_counter, ((au_dbuf_t *)argp)->aub_size,
+ ((au_dbuf_t *)argp)->aub_type, arg_size));
+
+ if (((au_dbuf_t *)argp)->aub_size < 1) {
+ warn_or_fatal(0,
+ gettext("invalid data length received from c2audit\n"));
+ goto input_exit;
+ }
+ /*
+ * is_blocked is true only if one or more plugins have "no
+ * continue" (-cnt) set and one of those has a full queue.
+ * All plugins block until success is met.
+ */
+ for (;;) {
+ DPRINT((dbfp, "%d input is calling queue_buffer\n",
+ call_counter));
+
+ is_blocked = queue_buffer((au_dbuf_t *)argp);
+
+ if (!is_blocked) {
+ adjust_priority();
+ break;
+ } else {
+ DPRINT((dbfp,
+ "%d input blocked (loop=%d)\n",
+ call_counter, loop_count));
+
+ wait_a_while();
+
+ DPRINT((dbfp, "%d input unblocked (loop=%d)\n",
+ call_counter, loop_count));
+ }
+#if DEBUG
+ loop_count++;
+#endif
+ }
+input_exit:
+ p = plugin_head;
+ while (p != NULL) {
+ (void) pthread_cond_signal(&(p->plg_cv));
+ p = p->plg_next;
+ }
+ ((au_dbuf_t *)argp)->aub_size = 0; /* return code */
+ (void) door_return(argp, sizeof (uint64_t), NULL, 0);
+}
+
+/*
+ * process() -- pass a buffer to a plugin
+ */
+static void
+process(plugin_t *p)
+{
+ int rc;
+ audit_rec_t *b_node;
+ audit_q_t *q_node;
+ auditd_rc_t plugrc;
+ char *error_string;
+ struct timespec delay;
+ int sendsignal;
+ int queue_len;
+ struct sched_param param;
+
+ DPRINT((dbfp, "%s is thread %d\n", p->plg_path, p->plg_tid));
+ p->plg_priority = param.sched_priority = BASE_PRIORITY;
+ (void) pthread_setschedparam(p->plg_tid, SCHED_OTHER, &param);
+
+ delay.tv_nsec = 0;
+
+ for (;;) {
+ retry_mode:
+
+ while (audit_dequeue(&(p->plg_queue), (void *)&q_node) != 0) {
+ DUMP("process", p, p->plg_last_seq_out, "blocked");
+ (void) pthread_cond_signal(&(in_thr.thd_cv));
+
+ (void) pthread_mutex_lock(&(p->plg_mutex));
+ p->plg_waiting++;
+ (void) pthread_cond_wait(&(p->plg_cv),
+ &(p->plg_mutex));
+ p->plg_waiting--;
+ (void) pthread_mutex_unlock(&(p->plg_mutex));
+
+ if (p->plg_removed)
+ break;
+
+ DUMP("process", p, p->plg_last_seq_out, "unblocked");
+ }
+ if (p->plg_removed)
+ break;
+#if DEBUG
+ if (q_node->aqq_sequence != p->plg_last_seq_out + 1)
+ (void) fprintf(dbfp,
+ "process(%d): buffer sequence=%u but prev=%u\n",
+ p->plg_tid, q_node->aqq_sequence,
+ p->plg_last_seq_out);
+#endif
+ error_string = NULL;
+
+ b_node = q_node->aqq_data;
+ plugrc = p->plg_fplugin(b_node->abq_buffer,
+ b_node->abq_data_len,
+ q_node->aqq_sequence, &error_string);
+#if DEBUG
+ p->plg_last_seq_out = q_node->aqq_sequence;
+#endif
+ switch (plugrc) {
+ case AUDITD_RETRY:
+ report_error(plugrc, error_string, p->plg_path);
+ free(error_string);
+ error_string = NULL;
+
+ DPRINT((dbfp, "process(%d) AUDITD_RETRY returned."
+ " cnt=%d (if 1, enter retry)\n",
+ p->plg_tid, p->plg_cnt));
+
+ if (p->plg_cnt) /* if cnt is on, lose the buffer */
+ break;
+
+ delay.tv_sec = p->plg_retry_time;
+ (void) pthread_mutex_lock(&(p->plg_mutex));
+ p->plg_waiting++;
+ (void) pthread_cond_reltimedwait_np(&(p->plg_cv),
+ &(p->plg_mutex), &delay);
+ p->plg_waiting--;
+ (void) pthread_mutex_unlock(&(p->plg_mutex));
+
+ DPRINT((dbfp, "left retry mode for %d\n", p->plg_tid));
+ goto retry_mode;
+
+ case AUDITD_SUCCESS:
+ p->plg_output++;
+ break;
+ default:
+ report_error(plugrc, error_string, p->plg_path);
+ free(error_string);
+ error_string = NULL;
+ break;
+ } /* end switch */
+ bpool_return(b_node);
+ qpool_return(p, q_node);
+
+ sendsignal = 0;
+ queue_len = audit_queue_size(&(p->plg_queue));
+
+ (void) pthread_mutex_lock(&(in_thr.thd_mutex));
+ if (in_thr.thd_waiting && (queue_len > p->plg_qmin) &&
+ (queue_len < p->plg_q_threshold))
+ sendsignal = 1;
+
+ (void) pthread_mutex_unlock(&(in_thr.thd_mutex));
+
+ if (sendsignal) {
+ (void) pthread_cond_signal(&(in_thr.thd_cv));
+ /*
+ * sched_yield(); does not help
+ * performance and in artificial tests
+ * (high sustained volume) appears to
+ * hurt by adding wide variability in
+ * the results.
+ */
+ } else if ((p->plg_priority < BASE_PRIORITY) &&
+ (queue_len < p->plg_q_threshold)) {
+ p->plg_priority = param.sched_priority =
+ BASE_PRIORITY;
+ (void) pthread_setschedparam(p->plg_tid, SCHED_OTHER,
+ &param);
+ }
+ } /* end for (;;) */
+ DUMP("process", p, p->plg_last_seq_out, "exit");
+ error_string = NULL;
+ if ((rc = p->plg_fplugin_close(&error_string)) !=
+ AUDITD_SUCCESS)
+ report_error(rc, error_string, p->plg_path);
+
+ free(error_string);
+
+ (void) pthread_mutex_lock(&plugin_mutex);
+ (void) unload_plugin(p);
+ (void) pthread_mutex_unlock(&plugin_mutex);
+}
diff --git a/usr/src/cmd/auditd/plugin.h b/usr/src/cmd/auditd/plugin.h
new file mode 100644
index 0000000000..0c4b24c6b4
--- /dev/null
+++ b/usr/src/cmd/auditd/plugin.h
@@ -0,0 +1,102 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ */
+
+#ifndef _PLUGIN_H
+#define _PLUGIN_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <security/auditd.h>
+#include "queue.h"
+
+typedef struct thd {
+ pthread_cond_t thd_cv;
+ pthread_mutex_t thd_mutex;
+ int thd_waiting;
+} thr_data_t;
+
+typedef struct plg plugin_t;
+struct plg {
+ boolean_t plg_initialized; /* if threads, pools created */
+ boolean_t plg_reopen; /* call auditd_plugin_open */
+ /*
+ * removed is 1 if last read of audit_control didn't list this
+ * plugin; it needs to be removed.
+ */
+ boolean_t plg_removed; /* plugin removed */
+ boolean_t plg_to_be_removed; /* tentative removal state */
+
+ char *plg_path; /* plugin path */
+ void *plg_dlptr; /* dynamic lib pointer */
+ auditd_rc_t (*plg_fplugin)(const char *, size_t, int, char **);
+ auditd_rc_t (*plg_fplugin_open)(const kva_t *, char **, char **);
+ auditd_rc_t (*plg_fplugin_close)(char **);
+
+ kva_t *plg_kvlist; /* plugin inputs */
+ size_t plg_qmax; /* max queue size */
+ size_t plg_qmin; /* min queue size */
+
+ uint32_t plg_sequence; /* buffer counter */
+ uint32_t plg_last_seq_out; /* buffer counter (debug) */
+ uint32_t plg_tossed; /* discards (debug) */
+ uint32_t plg_queued; /* count buffers queued */
+ uint32_t plg_output; /* count of buffers output */
+ int plg_priority; /* current priority */
+
+ au_queue_t plg_pool; /* buffer pool */
+ au_queue_t plg_queue; /* queue drawn from pool */
+ int plg_q_threshold; /* max preallocated queue */
+ audit_q_t *plg_save_q_copy; /* tmp holding for a record */
+
+ pthread_t plg_tid; /* thread id */
+ pthread_cond_t plg_cv;
+ pthread_mutex_t plg_mutex;
+ int plg_waiting; /* output thread wait state */
+
+ int plg_cnt; /* continue policy */
+
+ int plg_retry_time; /* retry (seconds) */
+
+ plugin_t *plg_next; /* null is end of list */
+};
+
+int auditd_thread_init();
+void auditd_thread_close();
+void auditd_exit(int);
+
+extern plugin_t *plugin_head;
+extern pthread_mutex_t plugin_mutex;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PLUGIN_H */
diff --git a/usr/src/cmd/auditd/queue.c b/usr/src/cmd/auditd/queue.c
new file mode 100644
index 0000000000..8e51030472
--- /dev/null
+++ b/usr/src/cmd/auditd/queue.c
@@ -0,0 +1,172 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <pthread.h>
+#include <memory.h>
+#include "queue.h"
+#include <stdio.h>
+#include <assert.h>
+#include "plugin.h"
+
+#define DEBUG 0
+
+#if DEBUG
+static FILE *dbfp;
+extern FILE *__auditd_debug_file_open();
+#define DPRINT(x) {(void) fprintf x; }
+#else
+#define DPRINT(x)
+#endif
+
+void
+audit_queue_init(au_queue_t *q)
+{
+ q->auq_head = NULL;
+ q->auq_tail = NULL;
+ (void) pthread_mutex_init(&q->auq_lock, NULL);
+ q->auq_count = 0;
+#if DEBUG
+ dbfp = __auditd_debug_file_open();
+#endif
+}
+
+/*
+ * enqueue() caller creates queue entry
+ */
+
+void
+audit_enqueue(au_queue_t *q, void *p)
+{
+ (void) pthread_mutex_lock(&q->auq_lock);
+
+ DPRINT((dbfp, "enqueue0(%X): p=%X, head=%X, tail=%X, count=%d\n",
+ q, p, q->auq_head, q->auq_tail, q->auq_count));
+
+ if (q->auq_head == NULL)
+ q->auq_head = p;
+ else {
+ DPRINT((dbfp, "\tindirect tail=%X\n",
+ &(((audit_link_t *)(q->auq_tail))->aln_next)));
+
+ ((audit_link_t *)(q->auq_tail))->aln_next = p;
+ }
+ q->auq_tail = p;
+ ((audit_link_t *)p)->aln_next = NULL;
+ q->auq_count++;
+
+ DPRINT((dbfp, "enqueue1(%X): p=%X, head=%X, tail=%X, "
+ "count=%d, pnext=%X\n",
+ q, p, q->auq_head, q->auq_tail, q->auq_count,
+ ((audit_link_t *)p)->aln_next));
+
+ (void) pthread_mutex_unlock(&q->auq_lock);
+}
+
+/*
+ * audit_dequeue() returns entry; caller is responsible for free
+ */
+
+int
+audit_dequeue(au_queue_t *q, void **p)
+{
+ (void) pthread_mutex_lock(&q->auq_lock);
+
+ if ((*p = q->auq_head) == NULL) {
+ DPRINT((dbfp, "dequeue1(%X): p=%X, head=%X, "
+ "tail=%X, count=%d\n",
+ q, *p, q->auq_head, q->auq_tail, q->auq_count));
+
+ (void) pthread_mutex_unlock(&q->auq_lock);
+ return (1);
+ }
+ q->auq_count--;
+
+ /* if *p is the last, next is NULL */
+ q->auq_head = ((audit_link_t *)*p)->aln_next;
+
+ DPRINT((dbfp, "dequeue0(%X): p=%X, head=%X, tail=%X, "
+ "count=%d, pnext=%X\n",
+ q, *p, q->auq_head, q->auq_tail, q->auq_count,
+ ((audit_link_t *)*p)->aln_next));
+
+ (void) pthread_mutex_unlock(&q->auq_lock);
+ return (0);
+}
+
+/*
+ * increment ref count
+ */
+void
+audit_incr_ref(pthread_mutex_t *l, audit_rec_t *p)
+{
+ (void) pthread_mutex_lock(l);
+ p->abq_ref_count++;
+ DPRINT((dbfp, "incr_ref: p=%X, count=%d\n",
+ p, p->abq_ref_count));
+ (void) pthread_mutex_unlock(l);
+}
+/*
+ * decrement reference count; if it reaches zero,
+ * return a pointer to it. Otherwise, return NULL.
+ */
+audit_rec_t *
+audit_release(pthread_mutex_t *l, audit_rec_t *p)
+{
+ assert(p != NULL);
+
+ (void) pthread_mutex_lock(l);
+
+ DPRINT((dbfp, "release: p=%X, count=%d\n",
+ p, p->abq_ref_count));
+
+ if (--(p->abq_ref_count) > 0) {
+ (void) pthread_mutex_unlock(l);
+ return (NULL);
+ }
+ (void) pthread_mutex_unlock(l);
+
+ return (p);
+}
+
+int
+audit_queue_size(au_queue_t *q)
+{
+ int size;
+
+ (void) pthread_mutex_lock(&q->auq_lock);
+ size = q->auq_count;
+ (void) pthread_mutex_unlock(&q->auq_lock);
+
+ return (size);
+}
+
+
+void
+audit_queue_destroy(au_queue_t *q)
+{
+ (void) pthread_mutex_destroy(&q->auq_lock);
+}
diff --git a/usr/src/cmd/auditd/queue.h b/usr/src/cmd/auditd/queue.h
new file mode 100644
index 0000000000..d4c171763a
--- /dev/null
+++ b/usr/src/cmd/auditd/queue.h
@@ -0,0 +1,89 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ */
+
+#ifndef _QUEUE_H
+#define _QUEUE_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pthread.h>
+#include <stddef.h>
+
+typedef struct aln audit_link_t;
+struct aln {
+ audit_link_t *aln_next;
+};
+
+/* one audit_rec_t per audit record */
+
+typedef struct abq audit_rec_t;
+struct abq {
+ audit_link_t abq_l;
+ int abq_ref_count;
+ size_t abq_buf_len; /* space allocated */
+ size_t abq_data_len; /* space used */
+ char abq_buffer[1]; /* variable length */
+};
+#define AUDIT_REC_HEADER offsetof(audit_rec_t, abq_buffer[0])
+
+/* one audit_q_t entry per audit record per plugin */
+
+typedef struct aqq audit_q_t; /* plugin queued data */
+struct aqq {
+ audit_link_t aqq_l;
+ audit_rec_t *aqq_data;
+ int aqq_sequence;
+};
+
+/* queue head */
+
+typedef struct auq au_queue_t;
+
+struct auq {
+ void *auq_head;
+ void *auq_tail;
+ int auq_count;
+ pthread_mutex_t auq_lock;
+};
+
+int audit_dequeue(au_queue_t *, void **);
+void audit_queue_destroy(au_queue_t *);
+void audit_enqueue(au_queue_t *, void *);
+int audit_queue_size(au_queue_t *);
+void audit_queue_init(au_queue_t *);
+audit_rec_t *audit_release(pthread_mutex_t *, audit_rec_t *);
+void audit_incr_ref(pthread_mutex_t *, audit_rec_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QUEUE_H */
diff --git a/usr/src/cmd/auditd/svc-auditd b/usr/src/cmd/auditd/svc-auditd
new file mode 100644
index 0000000000..cfeae6a5db
--- /dev/null
+++ b/usr/src/cmd/auditd/svc-auditd
@@ -0,0 +1,62 @@
+#! /sbin/sh
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+
+# if the audit state is "disabled" auditconfig returns
+# non-zero exit status unless the c2audit module is loaded;
+# if c2audit is loaded, "disabled" becomes "noaudit" early
+# in the boot cycle and "auditing" only after auditd starts.
+
+. /lib/svc/share/smf_include.sh
+
+AUDITCONFIG=/usr/sbin/auditconfig
+ZONE=`/sbin/zonename`
+
+AUDITCOND=`$AUDITCONFIG -getcond 2> /dev/null`
+
+if [ $? -ne 0 ]; then
+ # The decision whether to start
+ # auditing is driven by bsmconv / bsmunconv
+ /usr/sbin/svcadm mark maintenance system/auditd
+ exit $SMF_EXIT_MON_OFFLINE;
+fi
+
+# In a non-global zone, auditd is started only if the "perzone"
+# audit policy has been set.
+if [ "$ZONE" != "global" ]; then
+ echo `$AUDITCONFIG -getpolicy` | grep perzone > /dev/null
+
+ if [ $? -eq 1 ]; then
+ echo "$0: auditd is not configured to run in a local zone"
+ exit $SMF_EXIT_ERR_CONFIG;
+ fi
+fi
+
+/etc/security/audit_startup
+# daemon forks, parent exits when child says it's ready
+exec /usr/sbin/auditd
+