diff options
| author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
|---|---|---|
| committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
| commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
| tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/auditd | |
| download | illumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz | |
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/auditd')
| -rw-r--r-- | usr/src/cmd/auditd/Makefile | 87 | ||||
| -rw-r--r-- | usr/src/cmd/auditd/audit_sig_infc.h | 51 | ||||
| -rw-r--r-- | usr/src/cmd/auditd/auditd.c | 841 | ||||
| -rw-r--r-- | usr/src/cmd/auditd/auditd.xml | 152 | ||||
| -rw-r--r-- | usr/src/cmd/auditd/doorway.c | 1317 | ||||
| -rw-r--r-- | usr/src/cmd/auditd/plugin.h | 102 | ||||
| -rw-r--r-- | usr/src/cmd/auditd/queue.c | 172 | ||||
| -rw-r--r-- | usr/src/cmd/auditd/queue.h | 89 | ||||
| -rw-r--r-- | usr/src/cmd/auditd/svc-auditd | 62 |
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, + ¶m); + + /* 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, ¶m); + (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, ¶m); + } + 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, ¶m); + + 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, + ¶m); + } + } /* 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 + |
