summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr/src/cmd/cmd-inet/etc/services3
-rw-r--r--usr/src/lib/auditd_plugins/Makefile10
-rw-r--r--usr/src/lib/auditd_plugins/remote/Makefile57
-rw-r--r--usr/src/lib/auditd_plugins/remote/Makefile.com50
-rw-r--r--usr/src/lib/auditd_plugins/remote/audit_remote.c817
-rw-r--r--usr/src/lib/auditd_plugins/remote/audit_remote.h138
-rw-r--r--usr/src/lib/auditd_plugins/remote/i386/Makefile29
-rw-r--r--usr/src/lib/auditd_plugins/remote/mapfile-vers48
-rw-r--r--usr/src/lib/auditd_plugins/remote/sparc/Makefile29
-rw-r--r--usr/src/lib/auditd_plugins/remote/transport.c1560
-rw-r--r--usr/src/pkgdefs/SUNWcsl/prototype_com2
11 files changed, 2737 insertions, 6 deletions
diff --git a/usr/src/cmd/cmd-inet/etc/services b/usr/src/cmd/cmd-inet/etc/services
index 5bb0637df2..f9a8e77cc0 100644
--- a/usr/src/cmd/cmd-inet/etc/services
+++ b/usr/src/cmd/cmd-inet/etc/services
@@ -1,5 +1,5 @@
#
-# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
# CDDL HEADER START
@@ -143,3 +143,4 @@ mdns 5353/tcp
vnc-server 5900/tcp # VNC Server
dtspc 6112/tcp # CDE subprocess control
fs 7100/tcp # Font server
+solaris-audit 16162/tcp # Secure remote audit logging
diff --git a/usr/src/lib/auditd_plugins/Makefile b/usr/src/lib/auditd_plugins/Makefile
index eeadaad9c9..4f80c29acf 100644
--- a/usr/src/lib/auditd_plugins/Makefile
+++ b/usr/src/lib/auditd_plugins/Makefile
@@ -2,9 +2,8 @@
# 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.
+# Common Development and Distribution License (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.
@@ -20,15 +19,16 @@
# CDDL HEADER END
#
#
-# Copyright 2003 Sun Microsystems, Inc. All rights reserved.
+# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
-# ident "%Z%%M% %I% %E% SMI"
+#
include ../../Makefile.master
include ../Makefile.lib
SUBDIRS = binfile \
+ remote \
syslog
all := TARGET= all
diff --git a/usr/src/lib/auditd_plugins/remote/Makefile b/usr/src/lib/auditd_plugins/remote/Makefile
new file mode 100644
index 0000000000..e57261edb0
--- /dev/null
+++ b/usr/src/lib/auditd_plugins/remote/Makefile
@@ -0,0 +1,57 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+include $(SRC)/lib/Makefile.lib
+
+SUBDIRS = $(MACH)
+
+TEXT_DOMAIN= SUNW_OST_OSCMD
+POFILE= audit_remote.po
+MSGFILES= audit_remote.c transport.c
+
+all := TARGET= all
+clean := TARGET= clean
+clobber := TARGET= clobber
+install := TARGET= install
+lint := TARGET= lint
+
+.KEEP_STATE:
+
+all clean clobber install lint: $(SUBDIRS)
+
+$(POFILE): $(MSGFILES)
+ $(BUILDPO.msgfiles)
+
+_msg: $(MSGDOMAINPOFILE)
+
+
+$(SUBDIRS): FRC
+ @cd $@; pwd; $(MAKE) $(TARGET)
+
+FRC:
+
+include $(SRC)/lib/Makefile.targ
+include $(SRC)/Makefile.msg.targ
diff --git a/usr/src/lib/auditd_plugins/remote/Makefile.com b/usr/src/lib/auditd_plugins/remote/Makefile.com
new file mode 100644
index 0000000000..b8d4386412
--- /dev/null
+++ b/usr/src/lib/auditd_plugins/remote/Makefile.com
@@ -0,0 +1,50 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+LIBRARY= audit_remote.a
+VERS= .1
+OBJECTS= audit_remote.o transport.o
+
+LIBBSM= $(SRC)/lib/libbsm/common
+
+include $(SRC)/lib/Makefile.lib
+
+LIBS= $(DYNLIB)
+LDLIBS += -lbsm -lsecdb -lc -lnsl -lsocket -lgss -lmtmalloc
+
+CFLAGS += $(CCVERBOSE)
+CPPFLAGS += -D_REENTRANT -I$(LIBBSM)
+CPPFLAGS += -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
+
+ROOTLIBDIR= $(ROOT)/usr/lib/security
+
+.KEEP_STATE:
+
+all: $(LIBS)
+
+lint: lintcheck
+
+include ../../../Makefile.targ
diff --git a/usr/src/lib/auditd_plugins/remote/audit_remote.c b/usr/src/lib/auditd_plugins/remote/audit_remote.c
new file mode 100644
index 0000000000..2a66b292c4
--- /dev/null
+++ b/usr/src/lib/auditd_plugins/remote/audit_remote.c
@@ -0,0 +1,817 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * send audit records to remote host
+ *
+ */
+
+/*
+ * auditd_plugin_open(), auditd_plugin() and auditd_plugin_close()
+ * implement a replaceable library for use by auditd; they are a
+ * project private interface and may change without notice.
+ */
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <audit_plugin.h>
+#include <bsm/audit.h>
+#include <bsm/audit_record.h>
+#include <bsm/libbsm.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gssapi/gssapi.h>
+#include <libintl.h>
+#include <netdb.h>
+#include <pthread.h>
+#include <rpc/rpcsec_gss.h>
+#include <secdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <poll.h>
+
+#include "audit_remote.h"
+
+#define DEFAULT_RETRIES 3 /* default connection retries */
+#define DEFAULT_TIMEOUT 5 /* default connection timeout (in secs) */
+#define NOSUCCESS_DELAY 20 /* unsuccessful delivery to all p_hosts */
+
+#define FL_SET B_TRUE /* set_fdfl(): set the flag */
+#define FL_UNSET B_FALSE /* set_fdfl(): unset the flag */
+
+static int nosuccess_cnt; /* unsuccessful delivery counter */
+
+
+static int retries = DEFAULT_RETRIES; /* connection retries */
+int timeout = DEFAULT_TIMEOUT; /* connection timeout */
+static int timeout_p_timeout = -1; /* p_timeout attr storage */
+
+/* time reset mechanism; x .. timeout_p_timeout */
+#define RST_TIMEOUT(x) (x != -1 ? x : DEFAULT_TIMEOUT)
+
+/* semi-exponential timeout back off; x .. attempts, y .. timeout */
+#define BOFF_TIMEOUT(x, y) (x < 3 ? y * 2 * x : y * 8)
+
+/* general plugin lock */
+pthread_mutex_t plugin_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static struct hostlist_s *current_host;
+static struct hostlist_s *hosts;
+
+extern struct transq_hdr_s transq_hdr;
+static long transq_count_max;
+extern pthread_mutex_t transq_lock;
+
+extern pthread_t recv_tid;
+
+extern boolean_t notify_pipe_ready;
+extern int notify_pipe[2];
+
+#if DEBUG
+FILE *dfile; /* debug file */
+#endif
+
+/*
+ * set_transq_count_max() - sets the transq_count_max value based on kernel
+ * audit queue high water mark. This is backup solution for a case, when the
+ * plugin audit_control(4) option lacks (intentionally) the qsize option.
+ */
+static auditd_rc_t
+set_transq_count_max()
+{
+ struct au_qctrl qctrl;
+
+ if (auditon(A_GETQCTRL, (caddr_t)&qctrl, 0) != -1) {
+ transq_count_max = qctrl.aq_hiwater;
+ DPRINT((dfile, "Transmission queue max length set to %ld\n",
+ transq_count_max));
+ return (AUDITD_SUCCESS);
+ }
+
+ DPRINT((dfile, "Setting the transmission queue max length failed\n"));
+ return (AUDITD_RETRY);
+}
+
+/*
+ * get_port_default() - set the default port number; note, that "solaris-audit"
+ * used below in the code is the IANA assigned service name for the secure
+ * remote solaris audit logging.
+ */
+static auditd_rc_t
+get_port_default(int *port_default)
+{
+
+ struct servent serventry;
+ char serventry_buf[1024];
+
+ if (getservbyname_r("solaris-audit", "tcp", &serventry,
+ (char *)&serventry_buf, sizeof (serventry_buf)) == NULL) {
+ DPRINT((dfile, "unable to get default port number\n"));
+#if DEBUG
+ if (errno == ERANGE) {
+ DPRINT((dfile, "low on buffer\n"));
+ }
+#endif
+ return (AUDITD_INVALID);
+ }
+ *port_default = ntohs(serventry.s_port);
+ DPRINT((dfile, "default port: %d\n", *port_default));
+
+ return (AUDITD_SUCCESS);
+}
+
+/*
+ * trim_me() - trims the white space characters around the specified string.
+ * Inputs - pointer to the beginning of the string (str_ptr); returns - pointer
+ * to the trimmed string. Function returns NULL pointer in case of received
+ * empty string, NULL pointer or in case the pointed string consists of white
+ * space characters only.
+ */
+static char *
+trim_me(char *str_ptr) {
+
+ char *str_end;
+
+ if (str_ptr == NULL || *str_ptr == '\0') {
+ return (NULL);
+ }
+
+ while (isspace(*str_ptr)) {
+ str_ptr++;
+ }
+ if (*str_ptr == '\0') {
+ return (NULL);
+ }
+
+ str_end = str_ptr + strlen(str_ptr);
+
+ while (str_end > str_ptr && isspace(str_end[-1])) {
+ str_end--;
+ }
+ *str_end = '\0';
+
+ return (str_ptr);
+}
+
+
+/*
+ * parsehosts() end parses the host string (hosts_str)
+ */
+static auditd_rc_t
+parsehosts(char *hosts_str, char **error)
+{
+ char *hostportmech, *hpm;
+ char *hostname;
+ char *port_str;
+ char *mech_str;
+ int port;
+ int port_default = -1;
+ gss_OID mech_oid;
+ char *lasts_hpm;
+ hostlist_t *lasthost = NULL;
+ hostlist_t *newhost;
+ struct hostent *hostentry;
+ int error_num;
+ int rc;
+#if DEBUG
+ char addr_buf[INET6_ADDRSTRLEN];
+ int num_of_hosts = 0;
+#endif
+
+ hosts = lasthost;
+
+ DPRINT((dfile, "parsing %s\n", hosts_str));
+ while ((hostportmech = strtok_r(hosts_str, ",", &lasts_hpm)) != NULL) {
+
+ hosts_str = NULL;
+ hostname = NULL;
+ port_str = NULL;
+ port = port_default;
+ mech_str = NULL;
+ mech_oid = GSS_C_NO_OID;
+
+ DPRINT((dfile, "parsing host:port:mech %s\n", hostportmech));
+
+ if (strncmp(hostportmech, ":", 1 == 0)) { /* ":port:" case */
+ *error = strdup(gettext("no hostname specified"));
+ return (AUDITD_INVALID);
+ }
+
+ /* parse single host:port:mech target */
+ while ((hpm = strsep(&hostportmech, ":")) != NULL) {
+
+ if (hostname == NULL) {
+ hostname = hpm;
+ continue;
+ }
+ if (port_str == NULL) {
+ port_str = hpm;
+ continue;
+ }
+ if (mech_str == NULL) {
+ mech_str = hpm;
+ continue;
+ }
+
+ /* too many colons in the hostportmech string */
+ *error = strdup(gettext("invalid host:port:mech "
+ "specification"));
+ return (AUDITD_INVALID);
+ }
+
+ if (hostname == NULL || *hostname == '\0') {
+ *error = strdup(gettext("invalid hostname "
+ "specification"));
+ return (AUDITD_INVALID);
+ }
+
+ /* trim hostname */
+ hostname = trim_me(hostname);
+ if (hostname == NULL || *hostname == '\0') {
+ *error = strdup(gettext("empty hostname "
+ "specification"));
+ return (AUDITD_INVALID);
+ }
+
+ DPRINT((dfile, "resolving address for %s\n", hostname));
+
+ hostentry = getipnodebyname(hostname, AF_INET6, 0, &error_num);
+ if (!hostentry) {
+ hostentry = getipnodebyname(hostname, AF_INET, 0,
+ &error_num);
+ }
+ if (!hostentry) {
+ if (error_num == TRY_AGAIN) {
+ *error = strdup(gettext("host not found, "
+ "try later"));
+ return (AUDITD_RETRY);
+ } else {
+ *error = strdup(gettext("host not found"));
+ return (AUDITD_INVALID);
+ }
+ }
+ DPRINT((dfile, "hostentry: h_name=%s, addr_len=%d, addr=%s\n",
+ hostentry->h_name, hostentry->h_length,
+ inet_ntop(hostentry->h_addrtype,
+ hostentry->h_addr_list[0], addr_buf,
+ INET6_ADDRSTRLEN)));
+
+ /* trim port */
+ port_str = trim_me(port_str);
+ if (port_str == NULL || *port_str == '\0') {
+ if (port_default == -1 &&
+ (rc = get_port_default(&port_default))
+ != AUDITD_SUCCESS) {
+ *error = strdup(gettext(
+ "unable to get default port number"));
+ return (rc);
+ }
+ port = port_default;
+ DPRINT((dfile, "port: %d (default)\n", port));
+ } else {
+ errno = 0;
+ port = atoi(port_str);
+ if (errno != 0 || port < 1 || port > USHRT_MAX) {
+ *error = strdup(gettext("invalid port number"));
+ return (AUDITD_INVALID);
+ }
+ DPRINT((dfile, "port: %d\n", port));
+ }
+
+ /* trim mechanism */
+ mech_str = trim_me(mech_str);
+ if (mech_str != NULL && *mech_str != '\0') {
+ if (rpc_gss_mech_to_oid(mech_str, &mech_oid) != TRUE) {
+ *error = strdup(gettext("unknown mechanism"));
+ return (AUDITD_INVALID);
+ }
+ DPRINT((dfile, "mechanism: %s\n", mech_str));
+#if DEBUG
+ } else {
+ DPRINT((dfile, "mechanism: null (default)\n"));
+#endif
+ }
+
+ /* add this host to host list */
+ newhost = malloc(sizeof (hostlist_t));
+ if (newhost == NULL) {
+ *error = strdup(gettext("no memory"));
+ return (AUDITD_NO_MEMORY);
+ }
+ newhost->host = hostentry;
+ newhost->port = htons(port);
+ newhost->mech = mech_oid;
+ newhost->next_host = NULL;
+ if (lasthost != NULL) {
+ lasthost->next_host = newhost;
+ lasthost = lasthost->next_host;
+ } else {
+ lasthost = newhost;
+ hosts = newhost;
+ }
+#if DEBUG
+ num_of_hosts++;
+#endif
+ }
+
+ current_host = hosts;
+ DPRINT((dfile, "Configured %d hosts.\n", num_of_hosts));
+
+ return (AUDITD_SUCCESS);
+}
+
+
+/*
+ * Frees host list
+ */
+static void
+freehostlist()
+{
+ hostlist_t *h, *n;
+
+ (void) pthread_mutex_lock(&plugin_mutex);
+ h = hosts;
+ while (h) {
+ n = h->next_host;
+ freehostent(h->host);
+ free(h);
+ h = n;
+ }
+ current_host = NULL;
+ hosts = NULL;
+ (void) pthread_mutex_unlock(&plugin_mutex);
+}
+
+#if DEBUG
+static char *
+auditd_message(auditd_rc_t msg_code) {
+ char *rc_msg;
+
+ switch (msg_code) {
+ case AUDITD_SUCCESS:
+ rc_msg = strdup("ok");
+ break;
+ case AUDITD_RETRY:
+ rc_msg = strdup("retry after a delay");
+ break;
+ case AUDITD_NO_MEMORY:
+ rc_msg = strdup("can't allocate memory");
+ break;
+ case AUDITD_INVALID:
+ rc_msg = strdup("bad input");
+ break;
+ case AUDITD_COMM_FAIL:
+ rc_msg = strdup("communications failure");
+ break;
+ case AUDITD_FATAL:
+ rc_msg = strdup("other error");
+ break;
+ case AUDITD_FAIL:
+ rc_msg = strdup("other non-fatal error");
+ break;
+ }
+ return (rc_msg);
+}
+#endif
+
+/*
+ * rsn_to_msg() - translation of the reason of closure identifier to the more
+ * human readable/understandable form.
+ */
+static char *
+rsn_to_msg(close_rsn_t reason)
+{
+ char *rc_msg;
+
+ switch (reason) {
+ case RSN_UNDEFINED:
+ rc_msg = strdup(gettext("not defined reason of failure"));
+ break;
+ case RSN_INIT_POLL:
+ rc_msg = strdup(gettext("poll() initialization failed"));
+ break;
+ case RSN_TOK_RECV_FAILED:
+ rc_msg = strdup(gettext("token receiving failed"));
+ break;
+ case RSN_TOK_TOO_BIG:
+ rc_msg = strdup(gettext("unacceptable token size"));
+ break;
+ case RSN_TOK_UNVERIFIABLE:
+ rc_msg = strdup(gettext("received unverifiable token"));
+ break;
+ case RSN_SOCKET_CLOSE:
+ rc_msg = strdup(gettext("closed socket"));
+ break;
+ case RSN_SOCKET_CREATE:
+ rc_msg = strdup(gettext("socket creation failed"));
+ break;
+ case RSN_CONNECTION_CREATE:
+ rc_msg = strdup(gettext("connection creation failed"));
+ break;
+ case RSN_PROTOCOL_NEGOTIATE:
+ rc_msg = strdup(gettext("protocol negotiation failed"));
+ break;
+ case RSN_GSS_CTX_ESTABLISH:
+ rc_msg = strdup(gettext("context establishing failed"));
+ break;
+ case RSN_GSS_CTX_EXP:
+ rc_msg = strdup(gettext("context expired"));
+ break;
+ case RSN_UNKNOWN_AF:
+ rc_msg = strdup(gettext("unknown address family"));
+ break;
+ case RSN_MEMORY_ALLOCATE:
+ rc_msg = strdup(gettext("memory allocation failed"));
+ break;
+ default: /* RSN_OTHER_ERR */
+ rc_msg = strdup(gettext("other, not classified error"));
+ break;
+ }
+ return (rc_msg);
+}
+
+/*
+ * set_fdfl() - based on set_fl (FL_SET/FL_UNSET) un/sets the fl flag associated
+ * with fd file descriptor.
+ */
+static boolean_t
+set_fdfl(int fd, int fl, boolean_t set_fl)
+{
+ int flags;
+
+ /* power of two test - only single bit flags are allowed */
+ if (!fl || (fl & (fl-1))) {
+ DPRINT((dfile, "incorrect flag - %d isn't power of two\n", fl));
+ return (B_FALSE);
+ }
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
+ DPRINT((dfile, "cannot get file descriptor flags\n"));
+ return (B_FALSE);
+ }
+
+ if (set_fl) { /* set the fl flag */
+ if (flags & fl) {
+ return (B_TRUE);
+ }
+
+ flags |= fl;
+
+ } else { /* unset the fl flag */
+ if (~flags & fl) {
+ return (B_TRUE);
+ }
+
+ flags &= ~fl;
+ }
+
+ if (fcntl(fd, F_SETFL, flags) == -1) {
+ DPRINT((dfile, "cannot %s file descriptor flags\n",
+ (set_fl ? "set" : "unset")));
+ return (B_FALSE);
+ }
+
+ DPRINT((dfile, "fd: %d - flag: 0%o was %s\n", fd, fl,
+ (set_fl ? "set" : "unset")));
+ return (B_TRUE);
+}
+
+
+/*
+ * create_notify_pipe() - creates the notification pipe. Function returns
+ * B_TRUE/B_FALSE on success/failure.
+ */
+static boolean_t
+create_notify_pipe(int *notify_pipe, char **error)
+{
+
+ if (pipe(notify_pipe) < 0) {
+ DPRINT((dfile, "Cannot create notify pipe: %s\n",
+ strerror(errno)));
+ *error = strdup(gettext("failed to create notification pipe"));
+ return (B_FALSE);
+ } else {
+ DPRINT((dfile, "Pipe created in:%d out:%d\n", notify_pipe[0],
+ notify_pipe[1]));
+ /* make (only) the pipe "in" end nonblocking */
+ if (!set_fdfl(notify_pipe[0], O_NONBLOCK, FL_UNSET) ||
+ !set_fdfl(notify_pipe[1], O_NONBLOCK, FL_SET)) {
+ DPRINT((dfile, "Cannot prepare blocking scheme on top "
+ "of the notification pipe: %s\n", strerror(errno)));
+ (void) close(notify_pipe[0]);
+ (void) close(notify_pipe[1]);
+
+ *error = strdup(gettext("failed to prepare blocking "
+ "scheme on top of the notification pipe"));
+ return (B_FALSE);
+ }
+ }
+
+ return (B_TRUE);
+}
+
+
+/*
+ * auditd_plugin() sends a record via a tcp connection.
+ *
+ * Operation:
+ * - 1 tcp connection opened at a time, referenced by current_host->sockfd
+ * - tries to (open and) send a record to the current_host where its address
+ * is taken from the first hostent h_addr_list entry
+ * - if connection times out, tries second host
+ * - if all hosts where tried tries again for retries number of times
+ * - if everything fails, it bails out with AUDITD_RETRY
+ *
+ * Note, that space on stack allocated for any error message returned along
+ * with AUDITD_RETRY is subsequently freed by auditd.
+ *
+ */
+auditd_rc_t
+auditd_plugin(const char *input, size_t in_len, uint32_t sequence, char **error)
+{
+ int rc = AUDITD_FAIL;
+ int send_record_rc = SEND_RECORD_FAIL;
+ hostlist_t *start_host;
+ int attempts = 0;
+ char *ext_error; /* extended error string */
+ close_rsn_t err_rsn = RSN_UNDEFINED;
+ char *rsn_msg;
+
+#if DEBUG
+ char *rc_msg;
+ static uint32_t last_sequence = 0;
+
+ if ((last_sequence > 0) && (sequence != last_sequence + 1)) {
+ DPRINT((dfile, "audit_remote: buffer sequence=%d but prev=%d\n",
+ sequence, last_sequence));
+ }
+ last_sequence = sequence;
+
+ DPRINT((dfile, "audit_remote: input seq=%d, len=%d\n",
+ sequence, in_len));
+#endif
+
+ (void) pthread_mutex_lock(&transq_lock);
+
+ if (transq_hdr.count == transq_count_max) {
+ DPRINT((dfile, "Transmission queue is full (%ld)\n",
+ transq_hdr.count));
+ (void) pthread_mutex_unlock(&transq_lock);
+ *error = strdup(gettext("retransmission queue is full"));
+ return (AUDITD_RETRY);
+ }
+ (void) pthread_mutex_unlock(&transq_lock);
+
+
+ (void) pthread_mutex_lock(&plugin_mutex);
+
+ /* cycle over the hosts and possibly deliver the record */
+ start_host = current_host;
+ while (rc != AUDITD_SUCCESS) {
+ DPRINT((dfile, "Trying to send record to %s [attempt:%d/%d]\n",
+ current_host->host->h_name, attempts + 1, retries));
+
+ send_record_rc = send_record(current_host, input, in_len,
+ (uint64_t)sequence, &err_rsn);
+ DPRINT((dfile, "send_record() returned %d - ", send_record_rc));
+
+ switch (send_record_rc) {
+ case SEND_RECORD_SUCCESS:
+ DPRINT((dfile, "success\n"));
+ nosuccess_cnt = 0;
+ rc = AUDITD_SUCCESS;
+ break;
+ case SEND_RECORD_NEXT:
+ DPRINT((dfile, "retry the same host: %s (penalty)\n",
+ current_host->host->h_name));
+ attempts++;
+ break;
+ case SEND_RECORD_RETRY:
+ DPRINT((dfile, "retry the same host: %s (no penalty)\n",
+ current_host->host->h_name));
+ break;
+ }
+
+
+ if (send_record_rc == SEND_RECORD_NEXT) {
+
+ /* warn about unsuccessful auditd record delivery */
+ rsn_msg = rsn_to_msg(err_rsn);
+ (void) asprintf(&ext_error,
+ "retry %d connection %s:%d %s", attempts + 1,
+ current_host->host->h_name,
+ ntohs(current_host->port), rsn_msg);
+ if (ext_error == NULL) {
+ free(rsn_msg);
+ *error = strdup(gettext("no memory"));
+ rc = AUDITD_NO_MEMORY;
+ break;
+ }
+ __audit_dowarn2("plugin", "audit_remote.so",
+ gettext("auditd record delivery failed"),
+ ext_error, attempts + 1);
+ free(rsn_msg);
+ free(ext_error);
+
+
+ if (attempts < retries) {
+ /* semi-exponential timeout back off */
+ timeout = BOFF_TIMEOUT(attempts, timeout);
+ DPRINT((dfile, "New timeout=%d\n", timeout));
+ } else {
+ /* get next host */
+ current_host = current_host->next_host;
+ if (current_host == NULL) {
+ current_host = hosts;
+ }
+ timeout = RST_TIMEOUT(timeout_p_timeout);
+ DPRINT((dfile, "New timeout=%d\n", timeout));
+ attempts = 0;
+ }
+
+
+ /* one cycle finished */
+ if (current_host == start_host && attempts == 0) {
+ nosuccess_cnt++;
+ (void) asprintf(&ext_error, "all hosts defined "
+ "as p_hosts were tried to deliver "
+ "the audit record to with no success "
+ "- sleeping for %d seconds",
+ NOSUCCESS_DELAY);
+ if (ext_error == NULL) {
+ *error = strdup(gettext("no memory"));
+ rc = AUDITD_NO_MEMORY;
+ break;
+ }
+ __audit_dowarn2("plugin", "audit_remote.so",
+ "unsuccessful attempt to deliver audit "
+ "record",
+ ext_error, nosuccess_cnt);
+ free(ext_error);
+ (void) sleep(NOSUCCESS_DELAY);
+ }
+
+ } /* if (send_record_rc == SEND_RECORD_NEXT) */
+
+ err_rsn = RSN_UNDEFINED;
+
+ } /* while (rc != AUDITD_SUCCESS) */
+
+ (void) pthread_mutex_unlock(&plugin_mutex);
+
+#if DEBUG
+ rc_msg = auditd_message(rc);
+ DPRINT((dfile, "audit_remote: returning: %s\n", rc_msg));
+ free(rc_msg);
+#endif
+
+ return (rc);
+}
+
+/*
+ * auditd_plugin_open() may be called multiple times; on initial open or
+ * `audit -s`, then kvlist != NULL; on `audit -n`, then kvlist == NULL.
+ * For more information see audit(1M).
+ *
+ * Note, that space on stack allocated for any error message returned along
+ * with AUDITD_RETRY is subsequently freed by auditd.
+ *
+ */
+auditd_rc_t
+auditd_plugin_open(const kva_t *kvlist, char **ret_list, char **error)
+{
+ kva_t *kv;
+ char *val_str;
+ int val;
+ long val_l;
+ int rc = 0;
+
+ *error = NULL;
+ *ret_list = NULL;
+ kv = (kva_t *)kvlist;
+
+#if DEBUG
+ dfile = __auditd_debug_file_open();
+#endif
+
+ /* initial open or audit -s */
+ if (kvlist != NULL) {
+ DPRINT((dfile, "Action: initial open or `audit -s`\n"));
+ val_str = kva_match(kv, "p_timeout");
+ if (val_str != NULL) {
+ DPRINT((dfile, "val_str=%s\n", val_str));
+ errno = 0;
+ val = atoi(val_str);
+ if (errno == 0 && val >= 1) {
+ timeout_p_timeout = val;
+ timeout = val;
+ }
+ }
+
+ val_str = kva_match(kv, "p_retries");
+ if (val_str != NULL) {
+ DPRINT((dfile, "val_str=%s\n", val_str));
+ errno = 0;
+ val = atoi(val_str);
+ if (errno == 0 && val >= 0) {
+ retries = val;
+ }
+ }
+
+ val_str = kva_match(kv, "qsize");
+ if (val_str != NULL) {
+ DPRINT((dfile, "qsize=%s\n", val_str));
+ errno = 0;
+ val_l = atol(val_str);
+ if (errno == 0 && val_l > 0) {
+ transq_count_max = val_l;
+ }
+
+ } else {
+ DPRINT((dfile, "qsize not in kvlist\n"));
+ if ((rc = set_transq_count_max()) != AUDITD_SUCCESS) {
+ *error = strdup(gettext("cannot get kernel "
+ "auditd queue high water mark\n"));
+ return (rc);
+ }
+ }
+ DPRINT((dfile, "timeout=%d, retries=%d, transq_count_max=%ld\n",
+ timeout, retries, transq_count_max));
+
+ val_str = kva_match(kv, "p_hosts");
+ if (val_str == NULL) {
+ *error = strdup(gettext("no hosts configured"));
+ return (AUDITD_RETRY);
+ }
+ if ((rc = parsehosts(val_str, error)) != AUDITD_SUCCESS) {
+ return (rc);
+ }
+
+ /* create the notification pipe towards the receiving thread */
+ if (!notify_pipe_ready) {
+ if (create_notify_pipe(notify_pipe, error)) {
+ notify_pipe_ready = B_TRUE;
+ } else {
+ return (AUDITD_RETRY);
+ }
+ }
+
+#if DEBUG
+ } else { /* audit -n */
+ DPRINT((dfile, "Action: `audit -n`\n"));
+#endif
+ }
+
+ return (AUDITD_SUCCESS);
+}
+
+/*
+ * auditd_plugin_close() performs shutdown operations. The return values are
+ * used by auditd to output warnings via the audit_warn(1M) script and the
+ * string returned via "error_text", is passed to audit_warn.
+ *
+ * Note, that space on stack allocated for any error message returned along
+ * with AUDITD_RETRY is subsequently freed by auditd.
+ *
+ */
+auditd_rc_t
+auditd_plugin_close(char **error)
+{
+ reset_transport(DO_EXIT, DO_SYNC);
+ if (pthread_join(recv_tid, NULL) != 0) {
+ *error = strdup(gettext("unable to close receiving thread"));
+ return (AUDITD_RETRY);
+ }
+
+ freehostlist();
+ *error = NULL;
+ return (AUDITD_SUCCESS);
+}
diff --git a/usr/src/lib/auditd_plugins/remote/audit_remote.h b/usr/src/lib/auditd_plugins/remote/audit_remote.h
new file mode 100644
index 0000000000..69e27c98a0
--- /dev/null
+++ b/usr/src/lib/auditd_plugins/remote/audit_remote.h
@@ -0,0 +1,138 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ */
+
+#ifndef _AUDIT_REMOTE_H
+#define _AUDIT_REMOTE_H
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <security/auditd.h>
+
+/* gettext() obfuscation routine for lint */
+#ifdef __lint
+#define gettext(x) x
+#endif
+
+
+/* send_record() return code */
+enum send_record_rc {
+ SEND_RECORD_SUCCESS,
+ SEND_RECORD_NEXT,
+ SEND_RECORD_RETRY,
+ SEND_RECORD_FAIL
+};
+typedef enum send_record_rc send_record_rc_t;
+
+/* closing helpers - the reason of connection closure */
+enum close_rsn_e {
+ RSN_UNDEFINED, /* reason not defined */
+ RSN_INIT_POLL, /* poll() initialization failed */
+ RSN_TOK_RECV_FAILED, /* token receiving failed */
+ RSN_TOK_TOO_BIG, /* unacceptable token size */
+ RSN_TOK_UNVERIFIABLE, /* received unverifiable token */
+ RSN_SOCKET_CLOSE, /* socket closure */
+ RSN_SOCKET_CREATE, /* socket creation */
+ RSN_CONNECTION_CREATE, /* connection creation */
+ RSN_PROTOCOL_NEGOTIATE, /* protocol version negotiation */
+ RSN_GSS_CTX_ESTABLISH, /* establish GSS-API context */
+ RSN_GSS_CTX_EXP, /* expiration of the GSS-API context */
+ RSN_UNKNOWN_AF, /* unknown address family */
+ RSN_MEMORY_ALLOCATE, /* memory allocation failure */
+ RSN_OTHER_ERR /* other, not classified error */
+};
+typedef enum close_rsn_e close_rsn_t;
+
+/* linked list of remote audit hosts (servers) */
+typedef struct hostlist_s hostlist_t;
+struct hostlist_s {
+ hostlist_t *next_host;
+ struct hostent *host;
+ in_port_t port; /* TCP port number */
+ gss_OID mech; /* GSS mechanism - see mech(4) */
+};
+
+/* transq_t - single, already sent token in the transmit queue. */
+struct transq_node_s {
+ struct transq_node_s *next;
+ struct transq_node_s *prev;
+ gss_buffer_desc seq_token; /* seq num || plain token */
+ uint64_t seq_num; /* seq number */
+};
+typedef struct transq_node_s transq_node_t;
+
+/* transq_hdr_t - the transmit queue header structure */
+struct transq_hdr_s {
+ struct transq_node_s *head;
+ struct transq_node_s *end;
+ long count; /* amount of nodes in the queue */
+};
+typedef struct transq_hdr_s transq_hdr_t;
+
+/* pipe_msg_s - the notification pipe message */
+struct pipe_msg_s {
+ int sock_num; /* socket fd to be poll()ed and more */
+ boolean_t sync; /* call the sync routines */
+};
+typedef struct pipe_msg_s pipe_msg_t;
+
+
+/*
+ * Cross audit_remote plugin source code shared functions and bool parameters.
+ *
+ * reset_transport() helpers:
+ * arg1) DO_SYNC, DO_NOT_SYNC
+ * arg2) DO_EXIT, DO_CLOSE, DO_NOT_EXIT, DO_NOT_CLOSE
+ */
+#define DO_SYNC B_TRUE
+#define DO_NOT_SYNC B_FALSE
+#define DO_EXIT B_FALSE
+#define DO_CLOSE B_TRUE
+#define DO_NOT_EXIT B_CLOSE
+#define DO_NOT_CLOSE B_EXIT
+extern void reset_transport(boolean_t, boolean_t);
+extern send_record_rc_t send_record(struct hostlist_s *, const char *, size_t,
+ uint64_t, close_rsn_t *);
+
+#if DEBUG
+#define DPRINT(x) { (void) fprintf x; (void) fflush(dfile); }
+#else
+#define DPRINT(x)
+#endif
+
+#if DEBUG
+extern FILE *dfile;
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _AUDIT_REMOTE_H */
diff --git a/usr/src/lib/auditd_plugins/remote/i386/Makefile b/usr/src/lib/auditd_plugins/remote/i386/Makefile
new file mode 100644
index 0000000000..47878e05bc
--- /dev/null
+++ b/usr/src/lib/auditd_plugins/remote/i386/Makefile
@@ -0,0 +1,29 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+include ../Makefile.com
+
+install: all $(ROOTLIBS) $(ROOTLINKS)
diff --git a/usr/src/lib/auditd_plugins/remote/mapfile-vers b/usr/src/lib/auditd_plugins/remote/mapfile-vers
new file mode 100644
index 0000000000..d106c11906
--- /dev/null
+++ b/usr/src/lib/auditd_plugins/remote/mapfile-vers
@@ -0,0 +1,48 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+#
+# MAPFILE HEADER START
+#
+# WARNING: STOP NOW. DO NOT MODIFY THIS FILE.
+# Object versioning must comply with the rules detailed in
+#
+# usr/src/lib/README.mapfiles
+#
+# You should not be making modifications here until you've read the most current
+# copy of that file. If you need help, contact a gatekeeper for guidance.
+#
+# MAPFILE HEADER END
+#
+
+SUNWprivate_1.1 {
+ global:
+ auditd_plugin;
+ auditd_plugin_close;
+ auditd_plugin_open;
+ local:
+ *;
+};
diff --git a/usr/src/lib/auditd_plugins/remote/sparc/Makefile b/usr/src/lib/auditd_plugins/remote/sparc/Makefile
new file mode 100644
index 0000000000..47878e05bc
--- /dev/null
+++ b/usr/src/lib/auditd_plugins/remote/sparc/Makefile
@@ -0,0 +1,29 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+include ../Makefile.com
+
+install: all $(ROOTLIBS) $(ROOTLINKS)
diff --git a/usr/src/lib/auditd_plugins/remote/transport.c b/usr/src/lib/auditd_plugins/remote/transport.c
new file mode 100644
index 0000000000..15f43db7e7
--- /dev/null
+++ b/usr/src/lib/auditd_plugins/remote/transport.c
@@ -0,0 +1,1560 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * transport layer for audit_remote (handles connection establishment, gss
+ * context initialization, message encryption and verification)
+ *
+ */
+
+#include <assert.h>
+#include <audit_plugin.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gssapi/gssapi.h>
+#include <libintl.h>
+#include <mtmalloc.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <poll.h>
+#include <pthread.h>
+
+#include "audit_remote.h"
+
+
+static int sockfd = -1;
+static struct hostent *current_host;
+static gss_OID *current_mech_oid;
+static in_port_t current_port;
+static boolean_t flush_transq;
+
+static char *ver_str = "01"; /* supported protocol version */
+static char *ver_str_concat; /* concat serv/client version */
+
+static gss_ctx_id_t gss_ctx;
+static boolean_t gss_ctx_initialized;
+
+pthread_t recv_tid; /* receiving thread */
+static pthread_once_t recv_once_control = PTHREAD_ONCE_INIT;
+
+extern int timeout; /* connection timeout */
+
+extern pthread_mutex_t plugin_mutex;
+transq_hdr_t transq_hdr;
+
+/*
+ * The three locks synchronize the simultaneous actions on top of transmission
+ * queue, socket, gss_context.
+ */
+pthread_mutex_t transq_lock = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t sock_lock = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t gss_ctx_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* reset routine synchronization - required by the sending thread */
+pthread_mutex_t reset_lock = PTHREAD_MUTEX_INITIALIZER;
+static boolean_t reset_in_progress; /* reset routine in progress */
+
+#define NP_CLOSE -1 /* notification pipe - close message */
+#define NP_EXIT -2 /* notification pipe - exit message */
+boolean_t notify_pipe_ready;
+int notify_pipe[2]; /* notif. pipe - receiving thread */
+
+pthread_cond_t reset_cv = PTHREAD_COND_INITIALIZER;
+static close_rsn_t recv_closure_rsn;
+
+#define MAX_TOK_LEN (128 * 1000) /* max token length we accept (B) */
+
+/* transmission queue helpers */
+static void transq_dequeue(transq_node_t *);
+static boolean_t transq_enqueue(transq_node_t **, gss_buffer_t,
+ uint64_t);
+static int transq_retransmit(void);
+
+static boolean_t init_poll(int);
+static void do_reset(int *, struct pollfd *, boolean_t);
+static void do_cleanup(int *, struct pollfd *, boolean_t);
+
+static void init_recv_record(void);
+static void recv_record();
+static int connect_timeout(int, struct sockaddr *, int);
+static int send_timeout(int, const char *, size_t);
+static int recv_timeout(int, char *, size_t);
+static int send_token(int *, gss_buffer_t);
+static int recv_token(int, gss_buffer_t);
+
+
+/*
+ * report_err() - wrapper, mainly due to enhance the code readability - report
+ * error to syslog via call to __audit_syslog().
+ */
+static void
+report_err(char *msg)
+{
+ __audit_syslog("audit_remote.so", LOG_CONS | LOG_NDELAY, LOG_DAEMON,
+ LOG_ERR, msg);
+
+}
+
+
+/*
+ * report_gss_err() - GSS API error reporting
+ */
+static void
+report_gss_err(char *msg, OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+ gss_buffer_desc msg_buf;
+ OM_uint32 _min, msg_ctx;
+ char *err_msg;
+
+ /* major stat */
+ msg_ctx = 0;
+ do {
+ (void) gss_display_status(&_min, maj_stat, GSS_C_GSS_CODE,
+ *current_mech_oid, &msg_ctx, &msg_buf);
+ (void) asprintf(&err_msg,
+ gettext("GSS API error - %s(%u): %.*s\n"), msg, maj_stat,
+ msg_buf.length, (char *)msg_buf.value);
+ if (err_msg != NULL) {
+ report_err(err_msg);
+ free(err_msg);
+ }
+ (void) gss_release_buffer(&_min, &msg_buf);
+ } while (msg_ctx);
+
+ /* minor stat */
+ msg_ctx = 0;
+ do {
+ (void) gss_display_status(&_min, min_stat, GSS_C_MECH_CODE,
+ *current_mech_oid, &msg_ctx, &msg_buf);
+ (void) asprintf(&err_msg,
+ gettext("GSS mech error - %s(%u): %.*s\n"), msg, min_stat,
+ msg_buf.length, (char *)msg_buf.value);
+ if (err_msg != NULL) {
+ report_err(err_msg);
+ free(err_msg);
+ }
+ (void) gss_release_buffer(&_min, &msg_buf);
+ } while (msg_ctx);
+}
+
+/*
+ * prot_ver_negotiate() - negotiate/acknowledge the protocol version. Currently,
+ * there is only one version supported by the plugin - "01".
+ * Note: connection must be initiated prior version negotiation
+ */
+static int
+prot_ver_negotiate()
+{
+ gss_buffer_desc out_buf, in_buf;
+ size_t ver_str_concat_sz;
+
+ /*
+ * Set the version proposal string - once we support more than
+ * version "01" this part should be extended to solve the concatenation
+ * of supported version identifiers.
+ */
+ out_buf.value = (void *)ver_str;
+ out_buf.length = strlen((char *)out_buf.value);
+ DPRINT((dfile, "Protocol version proposal (size=%d): %.*s\n",
+ out_buf.length, out_buf.length, (char *)out_buf.value));
+
+ if (send_token(&sockfd, &out_buf) < 0) {
+ DPRINT((dfile, "Sending protocol version token failed\n"));
+ return (-1);
+ }
+
+ if (recv_token(sockfd, &in_buf) < 0) {
+ DPRINT((dfile, "Receiving protocol version token failed\n"));
+ return (-1);
+ }
+
+ /*
+ * Verify the sent/received string - memcmp() is sufficient here
+ * because we support only one version and it is represented by
+ * the "01" string. The received version has to be "01" string as well.
+ */
+ if (out_buf.length != in_buf.length ||
+ memcmp(out_buf.value, in_buf.value, out_buf.length) != 0) {
+ DPRINT((dfile, "Verification of the protocol version strings "
+ "failed [%d:%s][%d:%s]\n", out_buf.length,
+ (char *)out_buf.value, in_buf.length,
+ (char *)in_buf.value));
+ free(in_buf.value);
+ return (-1);
+ }
+
+ /*
+ * Prepare the concatenated client/server version strings later used
+ * as an application_data field in the gss_channel_bindings_struct
+ * structure.
+ */
+ ver_str_concat_sz = out_buf.length + in_buf.length + 1;
+ ver_str_concat = (char *)calloc(1, ver_str_concat_sz);
+ if (ver_str_concat == NULL) {
+ report_err(gettext("Memory allocation failed"));
+ DPRINT((dfile, "Memory allocation failed: %s\n",
+ strerror(errno)));
+ free(in_buf.value);
+ return (-1);
+ }
+ (void) memcpy(ver_str_concat, out_buf.value, out_buf.length);
+ (void) memcpy(ver_str_concat + out_buf.length, in_buf.value,
+ in_buf.length);
+ DPRINT((dfile, "Concatenated version strings: %s\n", ver_str_concat));
+
+ DPRINT((dfile, "Protocol version agreed.\n"));
+ free(in_buf.value);
+ return (0);
+}
+
+/*
+ * sock_prepare() - creates and connects socket. Function returns
+ * B_FALSE/B_TRUE on failure/success and sets the err_rsn accordingly to the
+ * reason of failure.
+ */
+static boolean_t
+sock_prepare(int *sockfdptr, struct hostent *host, close_rsn_t *err_rsn)
+{
+ struct sockaddr_storage addr;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ size_t addr_len;
+ int sock;
+
+ DPRINT((dfile, "Creating socket for %s\n", host->h_name));
+ bzero(&addr, sizeof (addr));
+ addr.ss_family = host->h_addrtype;
+ switch (host->h_addrtype) {
+ case AF_INET:
+ sin = (struct sockaddr_in *)&addr;
+ addr_len = sizeof (struct sockaddr_in);
+ bcopy(host->h_addr_list[0],
+ &(sin->sin_addr), sizeof (struct in_addr));
+ sin->sin_port = current_port;
+ break;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)&addr;
+ addr_len = sizeof (struct sockaddr_in6);
+ bcopy(host->h_addr_list[0],
+ &(sin6->sin6_addr), sizeof (struct in6_addr));
+ sin6->sin6_port = current_port;
+ break;
+ default:
+ /* unknown address family */
+ *err_rsn = RSN_UNKNOWN_AF;
+ return (B_FALSE);
+ }
+ if ((sock = socket(addr.ss_family, SOCK_STREAM, 0)) == -1) {
+ *err_rsn = RSN_SOCKET_CREATE;
+ return (B_FALSE);
+ }
+ DPRINT((dfile, "Socket created, fd=%d, connecting..\n", sock));
+
+ if (connect_timeout(sock, (struct sockaddr *)&addr, addr_len)) {
+ (void) close(sock);
+ *err_rsn = RSN_CONNECTION_CREATE;
+ return (B_FALSE);
+ }
+ *sockfdptr = sock;
+ DPRINT((dfile, "Connected to %s via fd=%d\n", host->h_name,
+ *sockfdptr));
+
+ return (B_TRUE);
+}
+
+/*
+ * establish_context() - establish the client/server GSS context.
+ *
+ * Note: connection must be established and version negotiated (in plain text)
+ * prior to establishing context.
+ */
+static int
+establish_context()
+{
+ gss_buffer_desc send_tok, recv_tok, *token_ptr;
+ OM_uint32 maj_stat, min_stat;
+ OM_uint32 init_sec_min_stat, ret_flags;
+ gss_name_t gss_name;
+ char *gss_svc_name = "audit";
+ char *svc_name;
+ size_t ver_str_concat_len;
+ struct gss_channel_bindings_struct input_chan_bindings;
+
+ /* GSS service name = gss_svc_name + "@" + remote hostname (fqdn) */
+ (void) asprintf(&svc_name, "%s@%s", gss_svc_name, current_host->h_name);
+ if (svc_name == NULL) {
+ report_err(gettext("Cannot allocate service name\n"));
+ DPRINT((dfile, "Memory allocation failed: %s\n",
+ strerror(errno)));
+ return (-1);
+ }
+ DPRINT((dfile, "Service name: %s\n", svc_name));
+
+ send_tok.value = svc_name;
+ send_tok.length = strlen(svc_name);
+ maj_stat = gss_import_name(&min_stat, &send_tok,
+ (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, &gss_name);
+ if (maj_stat != GSS_S_COMPLETE) {
+ report_gss_err(gettext("initializing context"), maj_stat,
+ min_stat);
+ free(svc_name);
+ return (-1);
+ }
+ token_ptr = GSS_C_NO_BUFFER;
+ gss_ctx = GSS_C_NO_CONTEXT;
+
+ /* initialize channel binding */
+ bzero(&input_chan_bindings, sizeof (input_chan_bindings));
+ ver_str_concat_len = strlen(ver_str_concat) + 1;
+ input_chan_bindings.application_data.length = ver_str_concat_len;
+ input_chan_bindings.application_data.value = ver_str_concat;
+
+ (void) pthread_mutex_lock(&gss_ctx_lock);
+ do {
+ maj_stat = gss_init_sec_context(&init_sec_min_stat,
+ GSS_C_NO_CREDENTIAL, &gss_ctx, gss_name, *current_mech_oid,
+ GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG
+ | GSS_C_CONF_FLAG, 0, &input_chan_bindings, token_ptr,
+ NULL, &send_tok, &ret_flags, NULL);
+
+ if (token_ptr != GSS_C_NO_BUFFER) {
+ (void) gss_release_buffer(&min_stat, &recv_tok);
+ }
+
+ if (send_tok.length != 0) {
+ DPRINT((dfile,
+ "Sending init_sec_context token (size=%d)\n",
+ send_tok.length));
+ if (send_token(&sockfd, &send_tok) < 0) {
+ free(svc_name);
+ (void) gss_release_name(&min_stat, &gss_name);
+ (void) pthread_mutex_unlock(&gss_ctx_lock);
+ return (-1);
+ }
+ }
+ if (send_tok.value != NULL) {
+ free(send_tok.value); /* freeing svc_name */
+ send_tok.value = NULL;
+ send_tok.length = 0;
+ }
+
+ if (maj_stat != GSS_S_COMPLETE &&
+ maj_stat != GSS_S_CONTINUE_NEEDED) {
+ report_gss_err(gettext("initializing context"),
+ maj_stat, init_sec_min_stat);
+ if (gss_ctx == GSS_C_NO_CONTEXT) {
+ (void) gss_delete_sec_context(&min_stat,
+ &gss_ctx, GSS_C_NO_BUFFER);
+ }
+ (void) gss_release_name(&min_stat, &gss_name);
+ (void) pthread_mutex_unlock(&gss_ctx_lock);
+ return (-1);
+ }
+
+ if (maj_stat == GSS_S_CONTINUE_NEEDED) {
+ DPRINT((dfile, "continue needed... "));
+ if (recv_token(sockfd, &recv_tok) < 0) {
+ (void) gss_release_name(&min_stat, &gss_name);
+ (void) pthread_mutex_unlock(&gss_ctx_lock);
+ return (-1);
+ }
+ token_ptr = &recv_tok;
+ }
+ } while (maj_stat == GSS_S_CONTINUE_NEEDED);
+ (void) gss_release_name(&min_stat, &gss_name);
+
+ DPRINT((dfile, "context established\n"));
+ (void) pthread_mutex_unlock(&gss_ctx_lock);
+ return (0);
+}
+
+/*
+ * delete_context() - release GSS context.
+ */
+static void
+delete_context()
+{
+ OM_uint32 min_stat;
+
+ (void) gss_delete_sec_context(&min_stat, &gss_ctx, GSS_C_NO_BUFFER);
+ DPRINT((dfile, "context deleted\n"));
+}
+
+/*
+ * send_token() - send GSS token over the wire.
+ */
+static int
+send_token(int *fdptr, gss_buffer_t tok)
+{
+ uint32_t len;
+ uint32_t lensz;
+ char *out_buf;
+ int fd;
+
+ (void) pthread_mutex_lock(&sock_lock);
+ if (*fdptr == -1) {
+ (void) pthread_mutex_unlock(&sock_lock);
+ DPRINT((dfile, "Socket detected as closed.\n"));
+ return (-1);
+ }
+ fd = *fdptr;
+
+ len = htonl(tok->length);
+ lensz = sizeof (len);
+
+ out_buf = (char *)malloc((size_t)(lensz + tok->length));
+ if (out_buf == NULL) {
+ (void) pthread_mutex_unlock(&sock_lock);
+ report_err(gettext("Memory allocation failed"));
+ DPRINT((dfile, "Memory allocation failed: %s\n",
+ strerror(errno)));
+ return (-1);
+ }
+ (void) memcpy((void *)out_buf, (void *)&len, lensz);
+ (void) memcpy((void *)(out_buf + lensz), (void *)tok->value,
+ tok->length);
+
+ if (send_timeout(fd, out_buf, (lensz + tok->length))) {
+ (void) pthread_mutex_unlock(&sock_lock);
+ free(out_buf);
+ return (-1);
+ }
+
+ (void) pthread_mutex_unlock(&sock_lock);
+ free(out_buf);
+ return (0);
+}
+
+
+/*
+ * recv_token() - receive GSS token over the wire.
+ */
+static int
+recv_token(int fd, gss_buffer_t tok)
+{
+ uint32_t len;
+
+ if (recv_timeout(fd, (char *)&len, sizeof (len))) {
+ return (-1);
+ }
+ len = ntohl(len);
+
+ /* simple DOS prevention mechanism */
+ if (len > MAX_TOK_LEN) {
+ report_err(gettext("Indicated invalid token length"));
+ DPRINT((dfile, "Indicated token length > %dB\n", MAX_TOK_LEN));
+ return (-1);
+ }
+
+ tok->value = (char *)malloc(len);
+ if (tok->value == NULL) {
+ report_err(gettext("Memory allocation failed"));
+ DPRINT((dfile, "Memory allocation failed: %s\n",
+ strerror(errno)));
+ tok->length = 0;
+ return (-1);
+ }
+
+ if (recv_timeout(fd, tok->value, len)) {
+ free(tok->value);
+ tok->value = NULL;
+ tok->length = 0;
+ return (-1);
+ }
+
+ tok->length = len;
+ return (0);
+}
+
+
+/*
+ * I/O functions
+ */
+
+/*
+ * connect_timeout() - sets nonblocking I/O on a socket and timeout-connects
+ */
+static int
+connect_timeout(int sockfd, struct sockaddr *name, int namelen)
+{
+ int flags;
+ struct pollfd fds;
+ int rc;
+ struct sockaddr_storage addr;
+ socklen_t addr_len = sizeof (addr);
+
+
+ flags = fcntl(sockfd, F_GETFL, 0);
+ if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ return (-1);
+ }
+ if (connect(sockfd, name, namelen)) {
+ if (!(errno == EINTR || errno == EINPROGRESS ||
+ errno == EWOULDBLOCK)) {
+ return (-1);
+ }
+ }
+ fds.fd = sockfd;
+ fds.events = POLLOUT;
+ for (;;) {
+ fds.revents = 0;
+ rc = poll(&fds, 1, timeout * 1000);
+ if (rc == 0) { /* timeout */
+ return (-1);
+ } else if (rc < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ } else {
+ return (-1);
+ }
+ }
+ if (fds.revents) {
+ if (getpeername(sockfd, (struct sockaddr *)&addr,
+ &addr_len))
+ return (-1);
+ } else {
+ return (-1);
+ }
+ return (0);
+ }
+}
+
+/*
+ * send_timeout() - send data (in chunks if needed, each chunk in timeout secs).
+ */
+static int
+send_timeout(int fd, const char *buf, size_t len)
+{
+ int bytes;
+ struct pollfd fds;
+ int rc;
+
+ fds.fd = fd;
+ fds.events = POLLOUT;
+
+ while (len) {
+ fds.revents = 0;
+ rc = poll(&fds, 1, timeout * 1000);
+ if (rc == 0) { /* timeout */
+ return (-1);
+ } else if (rc < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ } else {
+ return (-1);
+ }
+ }
+ if (!fds.revents) {
+ return (-1);
+ }
+
+ bytes = write(fd, buf, len);
+ if (bytes < 0) {
+ if (errno == EINTR) {
+ continue;
+ } else {
+ return (-1);
+ }
+ } else if (bytes == 0) { /* eof */
+ return (-1);
+ }
+
+ len -= bytes;
+ buf += bytes;
+ }
+
+ return (0);
+}
+
+/*
+ * recv_timeout() - receive data (in chunks if needed, each chunk in timeout
+ * secs). In case the function is called from receiving thread, the function
+ * cycles the poll() call in timeout seconds (waits for input from server).
+ */
+static int
+recv_timeout(int fd, char *buf, size_t len)
+{
+ int bytes;
+ struct pollfd fds;
+ int rc;
+
+ fds.fd = fd;
+ fds.events = POLLIN;
+
+ while (len) {
+ fds.revents = 0;
+ rc = poll(&fds, 1, timeout * 1000);
+ if (rc == 0) { /* timeout */
+ return (-1);
+ } else if (rc < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ } else {
+ return (-1);
+ }
+ }
+
+ if (!fds.revents) {
+ return (-1);
+ }
+
+ bytes = read(fd, buf, len);
+ if (bytes < 0) {
+ if (errno == EINTR) {
+ continue;
+ } else {
+ return (-1);
+ }
+ } else if (bytes == 0) { /* eof */
+ return (-1);
+ }
+
+ len -= bytes;
+ buf += bytes;
+ }
+
+ return (0);
+}
+
+/*
+ * read_fd() - reads data of length len from the given file descriptor fd to the
+ * buffer buf, in chunks if needed. Function returns B_FALSE on failure,
+ * otherwise B_TRUE. Function preserves errno, if it was set by the read(2).
+ */
+static boolean_t
+read_fd(int fd, char *buf, size_t len)
+{
+ int bytes;
+#ifdef DEBUG
+ size_t len_o = len;
+#endif
+
+ while (len) {
+ bytes = read(fd, buf, len);
+ if (bytes < 0) { /* err */
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ } else {
+ return (B_FALSE);
+ }
+ } else if (bytes == 0) { /* eof */
+ return (B_FALSE);
+ }
+
+ len -= bytes;
+ buf += bytes;
+ }
+
+ DPRINT((dfile, "read_fd: Read %d bytes.\n", len_o - len));
+ return (B_TRUE);
+}
+
+/*
+ * write_fd() - writes buf of length len to the opened file descriptor fd, in
+ * chunks if needed. The data from the pipe are processed in the receiving
+ * thread. Function returns B_FALSE on failure, otherwise B_TRUE. Function
+ * preserves errno, if it was set by the write(2).
+ */
+static boolean_t
+write_fd(int fd, char *buf, size_t len)
+{
+ int bytes;
+#ifdef DEBUG
+ size_t len_o = len;
+#endif
+
+ while (len) {
+ bytes = write(fd, buf, len);
+ if (bytes == -1) { /* err */
+ if (errno == EINTR || errno == EAGAIN) {
+ continue;
+ } else {
+ return (B_FALSE);
+ }
+ }
+
+ len -= bytes;
+ buf += bytes;
+ }
+
+ DPRINT((dfile, "write_fd: Wrote %d bytes.\n", len_o - len));
+ return (B_TRUE);
+}
+
+/*
+ * Plug-in entry point
+ */
+
+/*
+ * send_record() - send an audit record to a host opening a connection,
+ * negotiate version and establish context if necessary.
+ */
+send_record_rc_t
+send_record(struct hostlist_s *hostlptr, const char *input, size_t in_len,
+ uint64_t sequence, close_rsn_t *err_rsn)
+{
+ gss_buffer_desc in_buf, out_buf;
+ OM_uint32 maj_stat, min_stat;
+ int conf_state;
+ int rc;
+ transq_node_t *node_ptr;
+ uint64_t seq_n; /* sequence in the network byte order */
+ boolean_t init_sock_poll = B_FALSE;
+
+ /*
+ * We need to grab the reset_lock here, to prevent eventual
+ * unsynchronized cleanup calls within the reset routine (reset caused
+ * by the receiving thread) and the initialization calls in the
+ * send_record() code path.
+ */
+ (void) pthread_mutex_lock(&reset_lock);
+
+ /*
+ * Check whether the socket was closed by the recv thread prior to call
+ * send_record() and behave accordingly to the reason of the closure.
+ */
+ if (recv_closure_rsn != RSN_UNDEFINED) {
+ *err_rsn = recv_closure_rsn;
+ if (recv_closure_rsn == RSN_GSS_CTX_EXP) {
+ rc = SEND_RECORD_RETRY;
+ } else {
+ rc = SEND_RECORD_NEXT;
+ }
+ recv_closure_rsn = RSN_UNDEFINED;
+ (void) pthread_mutex_unlock(&reset_lock);
+ return (rc);
+ }
+
+ /*
+ * Send request to other then previously used host.
+ */
+ if (current_host != hostlptr->host) {
+ DPRINT((dfile, "Set new host: %s\n", hostlptr->host->h_name));
+ if (sockfd != -1) {
+ (void) pthread_mutex_unlock(&reset_lock);
+ reset_transport(DO_CLOSE, DO_SYNC);
+ return (SEND_RECORD_RETRY);
+ }
+ current_host = (struct hostent *)hostlptr->host;
+ current_mech_oid = &hostlptr->mech;
+ current_port = hostlptr->port;
+ }
+
+ /* initiate the receiving thread */
+ (void) pthread_once(&recv_once_control, init_recv_record);
+
+ /* create and connect() socket, negotiate the protocol version */
+ if (sockfd == -1) {
+ /* socket operations */
+ DPRINT((dfile, "Socket creation and connect\n"));
+ if (!sock_prepare(&sockfd, current_host, err_rsn)) {
+ /* we believe the err_rsn set by sock_prepare() */
+ (void) pthread_mutex_unlock(&reset_lock);
+ return (SEND_RECORD_NEXT);
+ }
+
+ /* protocol version negotiation */
+ DPRINT((dfile, "Protocol version negotiation\n"));
+ if (prot_ver_negotiate() != 0) {
+ DPRINT((dfile,
+ "Protocol version negotiation failed\n"));
+ (void) pthread_mutex_unlock(&reset_lock);
+ reset_transport(DO_CLOSE, DO_SYNC);
+ *err_rsn = RSN_PROTOCOL_NEGOTIATE;
+ return (SEND_RECORD_NEXT);
+ }
+
+ /* let the socket be initiated for poll() */
+ init_sock_poll = B_TRUE;
+ }
+
+ if (!gss_ctx_initialized) {
+ DPRINT((dfile, "Establishing context..\n"));
+ if (establish_context() != 0) {
+ (void) pthread_mutex_unlock(&reset_lock);
+ reset_transport(DO_CLOSE, DO_SYNC);
+ *err_rsn = RSN_GSS_CTX_ESTABLISH;
+ return (SEND_RECORD_NEXT);
+ }
+ gss_ctx_initialized = B_TRUE;
+ }
+
+ /* let the recv thread poll() on the sockfd */
+ if (init_sock_poll) {
+ init_sock_poll = B_FALSE;
+ if (!init_poll(sockfd)) {
+ *err_rsn = RSN_INIT_POLL;
+ (void) pthread_mutex_unlock(&reset_lock);
+ return (SEND_RECORD_RETRY);
+ }
+ }
+
+ (void) pthread_mutex_unlock(&reset_lock);
+
+ /* if not empty, retransmit contents of the transmission queue */
+ if (flush_transq) {
+ DPRINT((dfile, "Retransmitting remaining (%ld) tokens from "
+ "the transmission queue\n", transq_hdr.count));
+ if ((rc = transq_retransmit()) == 2) { /* gss context exp */
+ reset_transport(DO_CLOSE, DO_SYNC);
+ *err_rsn = RSN_GSS_CTX_EXP;
+ return (SEND_RECORD_RETRY);
+ } else if (rc == 1) {
+ reset_transport(DO_CLOSE, DO_SYNC);
+ *err_rsn = RSN_OTHER_ERR;
+ return (SEND_RECORD_NEXT);
+ }
+ flush_transq = B_FALSE;
+ }
+
+ /*
+ * Concatenate sequence number and the new record. Note, that the
+ * pointer to the chunk of memory allocated for the concatenated values
+ * is later passed to the transq_enqueu() function which stores the
+ * pointer in the transmission queue; subsequently called
+ * transq_dequeue() frees the allocated memory once the MIC is verified
+ * by the recv_record() function.
+ *
+ * If we return earlier than the transq_enqueue() is called, it's
+ * necessary to free the in_buf.value explicitly prior to return.
+ *
+ */
+ in_buf.length = in_len + sizeof (sequence);
+ in_buf.value = malloc(in_buf.length);
+ if (in_buf.value == NULL) {
+ report_err(gettext("Memory allocation failed"));
+ DPRINT((dfile, "Memory allocation failed: %s\n",
+ strerror(errno)));
+ reset_transport(DO_CLOSE, DO_SYNC);
+ *err_rsn = RSN_MEMORY_ALLOCATE;
+ return (SEND_RECORD_FAIL);
+ }
+ seq_n = htonll(sequence);
+ (void) memcpy(in_buf.value, &seq_n, sizeof (seq_n));
+ (void) memcpy((char *)in_buf.value + sizeof (seq_n), input, in_len);
+
+ /* wrap sequence number and the new record to the per-message token */
+ (void) pthread_mutex_lock(&gss_ctx_lock);
+ if (gss_ctx != NULL) {
+ maj_stat = gss_wrap(&min_stat, gss_ctx, 1, GSS_C_QOP_DEFAULT,
+ &in_buf, &conf_state, &out_buf);
+ (void) pthread_mutex_unlock(&gss_ctx_lock);
+ switch (maj_stat) {
+ case GSS_S_COMPLETE:
+ break;
+ case GSS_S_CONTEXT_EXPIRED:
+ reset_transport(DO_CLOSE, DO_SYNC);
+ free(in_buf.value);
+ *err_rsn = RSN_GSS_CTX_EXP;
+ return (SEND_RECORD_RETRY);
+ default:
+ report_gss_err(gettext("gss_wrap message"), maj_stat,
+ min_stat);
+ reset_transport(DO_CLOSE, DO_SYNC);
+ free(in_buf.value);
+ *err_rsn = RSN_OTHER_ERR;
+ return (SEND_RECORD_NEXT);
+ }
+ } else { /* GSS context deleted by the recv thread */
+ (void) pthread_mutex_unlock(&gss_ctx_lock);
+ reset_transport(DO_CLOSE, DO_SYNC);
+ free(in_buf.value);
+ *err_rsn = RSN_OTHER_ERR;
+ return (SEND_RECORD_NEXT);
+ }
+
+
+ /* enqueue the to-be-sent token into transmission queue */
+ (void) pthread_mutex_lock(&transq_lock);
+ if (!transq_enqueue(&node_ptr, &in_buf, sequence)) {
+ (void) pthread_mutex_unlock(&transq_lock);
+ reset_transport(DO_CLOSE, DO_SYNC);
+ free(in_buf.value);
+ (void) gss_release_buffer(&min_stat, &out_buf);
+ *err_rsn = RSN_OTHER_ERR;
+ return (SEND_RECORD_RETRY);
+ }
+ DPRINT((dfile, "Token enqueued for later verification\n"));
+ (void) pthread_mutex_unlock(&transq_lock);
+
+ /* send token */
+ if (send_token(&sockfd, &out_buf) < 0) {
+ DPRINT((dfile, "Token sending failed\n"));
+ reset_transport(DO_CLOSE, DO_SYNC);
+ (void) gss_release_buffer(&min_stat, &out_buf);
+
+ (void) pthread_mutex_lock(&transq_lock);
+ transq_dequeue(node_ptr);
+ (void) pthread_mutex_unlock(&transq_lock);
+
+ *err_rsn = RSN_OTHER_ERR;
+ return (SEND_RECORD_NEXT);
+ }
+ DPRINT((dfile, "Token sent (transq size = %ld)\n", transq_hdr.count));
+
+ (void) gss_release_buffer(&min_stat, &out_buf);
+
+ return (SEND_RECORD_SUCCESS);
+}
+
+/*
+ * init_recv_record() - initialize the receiver thread
+ */
+static void
+init_recv_record()
+{
+ DPRINT((dfile, "Initiating the recv thread\n"));
+ (void) pthread_create(&recv_tid, NULL, (void *(*)(void *))recv_record,
+ (void *)NULL);
+
+}
+
+
+/*
+ * recv_record() - the receiver thread routine
+ */
+static void
+recv_record()
+{
+ OM_uint32 maj_stat, min_stat;
+ gss_qop_t qop_state;
+ gss_buffer_desc in_buf = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc in_buf_mic = GSS_C_EMPTY_BUFFER;
+ transq_node_t *cur_node;
+ uint64_t r_seq_num; /* received sequence number */
+ boolean_t token_verified;
+ boolean_t break_flag;
+ struct pollfd fds[2];
+ int fds_cnt;
+ struct pollfd *pipe_fd = &fds[0];
+ struct pollfd *recv_fd = &fds[1];
+ uint32_t len;
+ int rc;
+ pipe_msg_t np_data;
+
+ DPRINT((dfile, "Receiver thread initiated\n"));
+
+ /*
+ * Fill in the information in the vector of file descriptors passed
+ * later on to the poll() function. In the initial state, there is only
+ * one struct pollfd in the vector which contains file descriptor of the
+ * notification pipe - notify_pipe[1]. There might be up to two file
+ * descriptors (struct pollfd) in the vector - notify_pipe[1] which
+ * resides in the vector during the entire life of the receiving thread,
+ * and the own file descriptor from which we read data sent by the
+ * remote server application.
+ */
+ pipe_fd->fd = notify_pipe[1];
+ pipe_fd->events = POLLIN;
+ recv_fd->fd = -1;
+ recv_fd->events = POLLIN;
+ fds_cnt = 1;
+
+ /*
+ * In the endless loop, try to grab some data from the socket or
+ * notify_pipe[1].
+ */
+ for (;;) {
+
+ pipe_fd->revents = 0;
+ recv_fd->revents = 0;
+ recv_closure_rsn = RSN_UNDEFINED;
+
+ /* block on poll, thus rc != 0 */
+ rc = poll(fds, fds_cnt, -1);
+ if (rc == -1) {
+ if (errno == EAGAIN || errno == EINTR) {
+ /* silently continue on EAGAIN || EINTR */
+ continue;
+ } else {
+ /* log the debug message in any other case */
+ DPRINT((dfile, "poll() failed: %s\n",
+ strerror(errno)));
+ report_err(gettext("poll() failed.\n"));
+ continue;
+ }
+ }
+
+ /*
+ * Receive a message from the notification pipe. Information
+ * from the notification pipe takes precedence over the received
+ * data from the remote server application.
+ *
+ * Notification pipe message format - message accepted
+ * from the notify pipe comprises of two parts (int ||
+ * boolean_t), where if the first part (sizeof (int)) equals
+ * NP_CLOSE, then the second part (sizeof (boolean_t)) signals
+ * the necessity of broadcasting (DO_SYNC/DO_NOT_SYNC) the end
+ * of the reset routine.
+ */
+ if (pipe_fd->revents & POLLIN) {
+ DPRINT((dfile, "An event on notify pipe detected\n"));
+ if (!read_fd(pipe_fd->fd, (char *)&np_data,
+ sizeof (np_data))) {
+ DPRINT((dfile, "Reading notify pipe failed: "
+ "%s\n", strerror(errno)));
+ report_err(gettext("Reading notify pipe "
+ "failed"));
+ } else {
+ switch (np_data.sock_num) {
+ case NP_EXIT: /* exit receiving thread */
+ do_cleanup(&fds_cnt, recv_fd,
+ np_data.sync);
+ pthread_exit((void *)NULL);
+ break;
+ case NP_CLOSE: /* close and remove recv_fd */
+ do_reset(&fds_cnt, recv_fd,
+ np_data.sync);
+ continue;
+ default: /* add rc_pipe to the fds */
+ recv_fd->fd = np_data.sock_num;
+ fds_cnt = 2;
+ continue;
+ }
+ }
+ }
+ /* Receive a token from the remote server application */
+ if (recv_fd->revents & POLLIN) {
+ DPRINT((dfile, "An event on fd detected\n"));
+ if (!read_fd(recv_fd->fd, (char *)&len, sizeof (len))) {
+ DPRINT((dfile, "Token length recv failed\n"));
+ recv_closure_rsn = RSN_TOK_RECV_FAILED;
+ reset_transport(DO_CLOSE, DO_NOT_SYNC);
+ continue;
+ }
+ len = ntohl(len);
+
+ /* simple DOS prevention mechanism */
+ if (len > MAX_TOK_LEN) {
+ report_err(gettext("Indicated invalid token "
+ "length"));
+ DPRINT((dfile, "Indicated token length > %dB\n",
+ MAX_TOK_LEN));
+ recv_closure_rsn = RSN_TOK_TOO_BIG;
+ reset_transport(DO_CLOSE, DO_NOT_SYNC);
+ continue;
+ }
+
+ in_buf.value = (char *)malloc(len);
+ if (in_buf.value == NULL) {
+ report_err(gettext("Memory allocation failed"));
+ DPRINT((dfile, "Memory allocation failed: %s\n",
+ strerror(errno)));
+ recv_closure_rsn = RSN_MEMORY_ALLOCATE;
+ reset_transport(DO_CLOSE, DO_NOT_SYNC);
+ continue;
+ }
+ if (!read_fd(recv_fd->fd, (char *)in_buf.value, len)) {
+ DPRINT((dfile, "Token value recv failed\n"));
+ free(in_buf.value);
+ recv_closure_rsn = RSN_TOK_RECV_FAILED;
+ reset_transport(DO_CLOSE, DO_NOT_SYNC);
+ continue;
+ }
+
+ in_buf.length = len;
+ }
+
+ /*
+ * Extract the sequence number and the MIC from
+ * the per-message token
+ */
+ (void) memcpy(&r_seq_num, in_buf.value, sizeof (r_seq_num));
+ r_seq_num = ntohll(r_seq_num);
+ in_buf_mic.length = in_buf.length - sizeof (r_seq_num);
+ in_buf_mic.value = (char *)in_buf.value + sizeof (r_seq_num);
+
+ /*
+ * seq_num/r_seq_num - the sequence number does not need to
+ * be unique in the transmission queue. Any token in the
+ * transmission queue with the same seq_num as the acknowledge
+ * token received from the server is tested. This is due to the
+ * fact that the plugin cannot influence (in the current
+ * implementation) sequence numbers generated by the kernel (we
+ * are reusing record sequence numbers as a transmission queue
+ * sequence numbers). The probability of having two or more
+ * tokens in the transmission queue is low and at the same time
+ * the performance gain due to using sequence numbers is quite
+ * high.
+ *
+ * In case a harder condition with regard to duplicate sequence
+ * numbers in the transmission queue will be desired over time,
+ * the break_flag behavior used below should be
+ * removed/changed_accordingly.
+ */
+ break_flag = B_FALSE;
+ token_verified = B_FALSE;
+ (void) pthread_mutex_lock(&transq_lock);
+ cur_node = transq_hdr.head;
+ while (cur_node != NULL && !break_flag) {
+ if (cur_node->seq_num != r_seq_num) {
+ cur_node = cur_node->next;
+ continue;
+ }
+
+ (void) pthread_mutex_lock(&gss_ctx_lock);
+ maj_stat = gss_verify_mic(&min_stat, gss_ctx,
+ &(cur_node->seq_token), &in_buf_mic,
+ &qop_state);
+ (void) pthread_mutex_unlock(&gss_ctx_lock);
+
+ if (!GSS_ERROR(maj_stat)) { /* the success case */
+ switch (maj_stat) {
+ /*
+ * All the GSS_S_OLD_TOKEN, GSS_S_UNSEQ_TOKEN,
+ * GSS_S_GAP_TOKEN are perceived as correct
+ * behavior of the server side. The plugin
+ * implementation is resistant to any of the
+ * above mention cases of returned status codes.
+ */
+ /*FALLTHRU*/
+ case GSS_S_OLD_TOKEN:
+ case GSS_S_UNSEQ_TOKEN:
+ case GSS_S_GAP_TOKEN:
+ case GSS_S_COMPLETE:
+ /*
+ * remove the verified record/node from
+ * the transmission queue
+ */
+ transq_dequeue(cur_node);
+ DPRINT((dfile, "Recv thread verified "
+ "the token (transq len = %ld)\n",
+ transq_hdr.count));
+
+ token_verified = B_TRUE;
+ break_flag = B_TRUE;
+ break;
+
+ /*
+ * Both the default case as well as
+ * GSS_S_DUPLICATE_TOKEN case should never
+ * occur. It's been left here for the sake of
+ * completeness.
+ * If any of the two cases occur, it is
+ * subsequently cought because we don't set
+ * the token_verified flag.
+ */
+ /*FALLTHRU*/
+ case GSS_S_DUPLICATE_TOKEN:
+ default:
+ break_flag = B_TRUE;
+ break;
+ } /* switch (maj_stat) */
+
+ } else { /* the failure case */
+ report_gss_err(
+ gettext("signature verification of the "
+ "received token failed"),
+ maj_stat, min_stat);
+
+ switch (maj_stat) {
+ case GSS_S_CONTEXT_EXPIRED:
+ /* retransmission necessary */
+ recv_closure_rsn = RSN_GSS_CTX_EXP;
+ break_flag = B_TRUE;
+ DPRINT((dfile, "Recv thread detected "
+ "the GSS context expiration\n"));
+ break;
+ case GSS_S_BAD_SIG:
+ DPRINT((dfile, "Bad signature "
+ "detected (seq_num = %lld)\n",
+ cur_node->seq_num));
+ cur_node = cur_node->next;
+ break;
+ default:
+ report_gss_err(
+ gettext("signature verification"),
+ maj_stat, min_stat);
+ break_flag = B_TRUE;
+ break;
+ }
+ }
+
+ } /* while */
+ (void) pthread_mutex_unlock(&transq_lock);
+
+ if (in_buf.value != NULL) {
+ free(in_buf.value);
+ in_buf.value = NULL;
+ in_buf.length = 0;
+ }
+
+ if (!token_verified) {
+ /*
+ * Received, but unverifiable token is perceived as
+ * the protocol flow corruption with the penalty of
+ * reinitializing the client/server connection.
+ */
+ DPRINT((dfile, "received unverifiable token\n"));
+ report_err(gettext("received unverifiable token\n"));
+ if (recv_closure_rsn == RSN_UNDEFINED) {
+ recv_closure_rsn = RSN_TOK_UNVERIFIABLE;
+ }
+ reset_transport(DO_CLOSE, DO_NOT_SYNC);
+ }
+
+ } /* for (;;) */
+
+
+}
+
+
+/*
+ * init_poll() - initiates the polling in the receiving thread via sending the
+ * appropriate message over the notify pipe. Message format = (int ||
+ * booleant_t), where the first part (sizeof (int)) contains the
+ * newly_opened/to_be_polled socket file descriptor. The contents of the second
+ * part (sizeof (boolean_t)) of the message works only as a padding here and no
+ * action (no recv/send thread synchronisation) is made in the receiving thread
+ * based on its value.
+ */
+static boolean_t
+init_poll(int fd)
+{
+ pipe_msg_t np_data;
+ int pipe_in = notify_pipe[0];
+
+ np_data.sock_num = fd;
+ np_data.sync = B_FALSE; /* padding only */
+
+ if (!write_fd(pipe_in, (char *)&np_data, sizeof (np_data))) {
+ DPRINT((dfile, "Cannot write to the notify pipe\n"));
+ report_err(gettext("writing to the notify pipe failed"));
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+
+/*
+ * reset_transport() - locked by the reset_lock initiates the reset of socket,
+ * GSS security context and (possibly) flags the transq for retransmission; for
+ * more detailed information see do_reset(). The reset_transport() also allows
+ * the synchronization - waiting for the reset to be finished.
+ *
+ * do_close: DO_SYNC, DO_NOT_SYNC
+ * sync_on_return: DO_EXIT (DO_NOT_CLOSE), DO_CLOSE (DO_NOT_EXIT)
+ *
+ */
+void
+reset_transport(boolean_t do_close, boolean_t sync_on_return)
+{
+ int pipe_in = notify_pipe[0];
+ pipe_msg_t np_data;
+
+ /*
+ * Check if the reset routine is in progress or whether it was already
+ * executed by some other thread.
+ */
+ (void) pthread_mutex_lock(&reset_lock);
+ if (reset_in_progress) {
+ (void) pthread_mutex_unlock(&reset_lock);
+ return;
+ }
+ reset_in_progress = B_TRUE;
+
+ np_data.sock_num = (do_close ? NP_CLOSE : NP_EXIT);
+ np_data.sync = sync_on_return;
+ (void) write_fd(pipe_in, (char *)&np_data, sizeof (np_data));
+
+ if (sync_on_return) {
+ while (reset_in_progress) {
+ (void) pthread_cond_wait(&reset_cv, &reset_lock);
+ DPRINT((dfile, "Wait for sync\n"));
+ }
+ DPRINT((dfile, "Synced\n"));
+ }
+ (void) pthread_mutex_unlock(&reset_lock);
+
+}
+
+
+/*
+ * do_reset() - the own reseting routine called from the recv thread. If the
+ * synchronization was requested, signal the finish via conditional variable.
+ */
+static void
+do_reset(int *fds_cnt, struct pollfd *recv_fd, boolean_t do_signal)
+{
+
+ (void) pthread_mutex_lock(&reset_lock);
+
+ /* socket */
+ (void) pthread_mutex_lock(&sock_lock);
+ if (sockfd == -1) {
+ DPRINT((dfile, "socket already closed\n"));
+ (void) pthread_mutex_unlock(&sock_lock);
+ goto out;
+ } else {
+ (void) close(sockfd);
+ sockfd = -1;
+ recv_fd->fd = -1;
+ (void) pthread_mutex_unlock(&sock_lock);
+ }
+ *fds_cnt = 1;
+
+ /* context */
+ if (gss_ctx_initialized) {
+ delete_context();
+ }
+ gss_ctx_initialized = B_FALSE;
+ gss_ctx = NULL;
+
+ /* mark transq to be flushed */
+ (void) pthread_mutex_lock(&transq_lock);
+ if (transq_hdr.count > 0) {
+ flush_transq = B_TRUE;
+ }
+ (void) pthread_mutex_unlock(&transq_lock);
+
+out:
+ reset_in_progress = B_FALSE;
+ if (do_signal) {
+ (void) pthread_cond_broadcast(&reset_cv);
+ }
+
+ (void) pthread_mutex_unlock(&reset_lock);
+}
+
+/*
+ * do_cleanup() - removes all the preallocated space by the plugin; prepares the
+ * plugin/application to be gracefully finished. Even thought the function
+ * allows execution without signalling the successful finish, it's recommended
+ * to use it (we usually want to wait for cleanup before exiting).
+ */
+static void
+do_cleanup(int *fds_cnt, struct pollfd *recv_fd, boolean_t do_signal)
+{
+
+ (void) pthread_mutex_lock(&reset_lock);
+
+ /*
+ * socket
+ * note: keeping locking for safety, thought it shouldn't be necessary
+ * in current implementation - we get here only in case the sending code
+ * path calls auditd_plugin_close() (thus no socket manipulation) and
+ * the recv thread is doing the own socket closure.
+ */
+ (void) pthread_mutex_lock(&sock_lock);
+ if (sockfd != -1) {
+ DPRINT((dfile, "Closing socket: %d\n", sockfd));
+ (void) close(sockfd);
+ sockfd = -1;
+ recv_fd->fd = -1;
+ }
+ *fds_cnt = 1;
+ (void) pthread_mutex_unlock(&sock_lock);
+
+ /* context */
+ if (gss_ctx_initialized) {
+ DPRINT((dfile, "Deleting context: "));
+ delete_context();
+ }
+ gss_ctx_initialized = B_FALSE;
+ gss_ctx = NULL;
+
+ /* transmission queue */
+ (void) pthread_mutex_lock(&transq_lock);
+ if (transq_hdr.count > 0) {
+ DPRINT((dfile, "Deallocating the transmission queue "
+ "(len = %ld)\n", transq_hdr.count));
+ while (transq_hdr.count > 0) {
+ transq_dequeue(transq_hdr.head);
+ }
+ }
+ (void) pthread_mutex_unlock(&transq_lock);
+
+ /* notification pipe */
+ if (notify_pipe_ready) {
+ (void) close(notify_pipe[0]);
+ (void) close(notify_pipe[1]);
+ notify_pipe_ready = B_FALSE;
+ }
+
+ reset_in_progress = B_FALSE;
+ if (do_signal) {
+ (void) pthread_cond_broadcast(&reset_cv);
+ }
+ (void) pthread_mutex_unlock(&reset_lock);
+}
+
+
+/*
+ * transq_dequeue() - dequeues given node pointed by the node_ptr from the
+ * transmission queue. Transmission queue should be locked prior to use of this
+ * function.
+ */
+static void
+transq_dequeue(transq_node_t *node_ptr)
+{
+
+ if (node_ptr == NULL) {
+ DPRINT((dfile, "transq_dequeue(): called with NULL pointer\n"));
+ return;
+ }
+
+ free(node_ptr->seq_token.value);
+
+ if (node_ptr->prev != NULL) {
+ node_ptr->prev->next = node_ptr->next;
+ }
+ if (node_ptr->next != NULL) {
+ node_ptr->next->prev = node_ptr->prev;
+ }
+
+
+ /* update the transq_hdr */
+ if (node_ptr->next == NULL) {
+ transq_hdr.end = node_ptr->prev;
+ }
+ if (node_ptr->prev == NULL) {
+ transq_hdr.head = node_ptr->next;
+ }
+
+ transq_hdr.count--;
+
+ free(node_ptr);
+}
+
+
+/*
+ * transq_enqueue() - creates new node in (at the end of) the transmission
+ * queue. in_ptoken_ptr is a pointer to the plain token in a form of
+ * gss_buffer_desc. Function returns 0 on success and updates the *node_ptr to
+ * point to a newly added transmission queue node. In case of any failure
+ * function returns 1 and sets the *node_ptr to NULL.
+ * Transmission queue should be locked prior to use of this function.
+ */
+static boolean_t
+transq_enqueue(transq_node_t **node_ptr, gss_buffer_t in_seqtoken_ptr,
+ uint64_t sequence)
+{
+
+ *node_ptr = calloc(1, sizeof (transq_node_t));
+ if (*node_ptr == NULL) {
+ report_err(gettext("Memory allocation failed"));
+ DPRINT((dfile, "Memory allocation failed: %s\n",
+ strerror(errno)));
+ goto errout;
+ }
+
+ /* value of the seq_token.value = (sequence number || plain token) */
+ (*node_ptr)->seq_num = sequence;
+ (*node_ptr)->seq_token.length = in_seqtoken_ptr->length;
+ (*node_ptr)->seq_token.value = in_seqtoken_ptr->value;
+
+ /* update the transq_hdr */
+ if (transq_hdr.head == NULL) {
+ transq_hdr.head = *node_ptr;
+ }
+ if (transq_hdr.end != NULL) {
+ (transq_hdr.end)->next = *node_ptr;
+ (*node_ptr)->prev = transq_hdr.end;
+ }
+ transq_hdr.end = *node_ptr;
+
+ transq_hdr.count++;
+
+ return (B_TRUE);
+
+errout:
+ if (*node_ptr != NULL) {
+ if ((*node_ptr)->seq_token.value != NULL) {
+ free((*node_ptr)->seq_token.value);
+ }
+ free(*node_ptr);
+ *node_ptr = NULL;
+ }
+ return (B_FALSE);
+}
+
+
+/*
+ * transq_retransmit() - traverse the transmission queue and try to, 1 by 1,
+ * re-wrap the tokens with the recent context information and retransmit the
+ * tokens from the transmission queue.
+ * Function returns 2 on GSS context expiration, 1 on any other error, 0 on
+ * successfully resent transmission queue.
+ */
+static int
+transq_retransmit()
+{
+
+ OM_uint32 maj_stat, min_stat;
+ transq_node_t *cur_node = transq_hdr.head;
+ gss_buffer_desc out_buf;
+ int conf_state;
+
+ DPRINT((dfile, "Retransmission of the remainder in the transqueue\n"));
+
+ while (cur_node != NULL) {
+
+ (void) pthread_mutex_lock(&transq_lock);
+ (void) pthread_mutex_lock(&gss_ctx_lock);
+ maj_stat = gss_wrap(&min_stat, gss_ctx, 1, GSS_C_QOP_DEFAULT,
+ &(cur_node->seq_token), &conf_state, &out_buf);
+ (void) pthread_mutex_unlock(&gss_ctx_lock);
+
+ switch (maj_stat) {
+ case GSS_S_COMPLETE:
+ break;
+ case GSS_S_CONTEXT_EXPIRED:
+ DPRINT((dfile, "Context expired.\n"));
+ report_gss_err(gettext("gss_wrap message"), maj_stat,
+ min_stat);
+ (void) pthread_mutex_unlock(&transq_lock);
+ return (2);
+ default:
+ report_gss_err(gettext("gss_wrap message"), maj_stat,
+ min_stat);
+ (void) pthread_mutex_unlock(&transq_lock);
+ return (1);
+ }
+
+ DPRINT((dfile, "Sending transmission queue token (seq=%lld, "
+ "size=%d, transq len=%ld)\n", cur_node->seq_num,
+ out_buf.length, transq_hdr.count));
+ if (send_token(&sockfd, &out_buf) < 0) {
+ (void) gss_release_buffer(&min_stat, &out_buf);
+ (void) pthread_mutex_unlock(&transq_lock);
+ return (1);
+ }
+ (void) gss_release_buffer(&min_stat, &out_buf);
+
+ cur_node = cur_node->next;
+ (void) pthread_mutex_unlock(&transq_lock);
+
+ } /* while */
+
+ return (0);
+}
diff --git a/usr/src/pkgdefs/SUNWcsl/prototype_com b/usr/src/pkgdefs/SUNWcsl/prototype_com
index d5918f5883..90e8a4bb5d 100644
--- a/usr/src/pkgdefs/SUNWcsl/prototype_com
+++ b/usr/src/pkgdefs/SUNWcsl/prototype_com
@@ -352,6 +352,8 @@ f none usr/lib/security/audit_binfile.so.1 755 root bin
s none usr/lib/security/audit_binfile.so=./audit_binfile.so.1
f none usr/lib/security/audit_syslog.so.1 755 root bin
s none usr/lib/security/audit_syslog.so=./audit_syslog.so.1
+f none usr/lib/security/audit_remote.so.1 755 root bin
+s none usr/lib/security/audit_remote.so=./audit_remote.so.1
f none usr/lib/security/pkcs11_kernel.so.1 755 root bin
s none usr/lib/security/pkcs11_kernel.so=./pkcs11_kernel.so.1
f none usr/lib/security/pkcs11_softtoken.so.1 755 root bin