changeset 10188:f6fea79fdd1c

4009451 need mechanism for putting audit trail on remote machine
author Jan Friedel <Jan.Friedel@Sun.COM>
date Tue, 28 Jul 2009 09:41:22 +0200
parents ad62e2dfbe0c
children 526ecf94a610
files usr/src/cmd/cmd-inet/etc/services usr/src/lib/auditd_plugins/Makefile usr/src/lib/auditd_plugins/remote/Makefile usr/src/lib/auditd_plugins/remote/Makefile.com usr/src/lib/auditd_plugins/remote/audit_remote.c usr/src/lib/auditd_plugins/remote/audit_remote.h usr/src/lib/auditd_plugins/remote/i386/Makefile usr/src/lib/auditd_plugins/remote/mapfile-vers usr/src/lib/auditd_plugins/remote/sparc/Makefile usr/src/lib/auditd_plugins/remote/transport.c usr/src/pkgdefs/SUNWcsl/prototype_com
diffstat 11 files changed, 2737 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/cmd-inet/etc/services	Mon Jul 27 23:54:47 2009 -0700
+++ b/usr/src/cmd/cmd-inet/etc/services	Tue Jul 28 09:41:22 2009 +0200
@@ -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 @@
 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
--- a/usr/src/lib/auditd_plugins/Makefile	Mon Jul 27 23:54:47 2009 -0700
+++ b/usr/src/lib/auditd_plugins/Makefile	Tue Jul 28 09:41:22 2009 +0200
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/auditd_plugins/remote/Makefile	Tue Jul 28 09:41:22 2009 +0200
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/auditd_plugins/remote/Makefile.com	Tue Jul 28 09:41:22 2009 +0200
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/auditd_plugins/remote/audit_remote.c	Tue Jul 28 09:41:22 2009 +0200
@@ -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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/auditd_plugins/remote/audit_remote.h	Tue Jul 28 09:41:22 2009 +0200
@@ -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 */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/auditd_plugins/remote/i386/Makefile	Tue Jul 28 09:41:22 2009 +0200
@@ -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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/auditd_plugins/remote/mapfile-vers	Tue Jul 28 09:41:22 2009 +0200
@@ -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:
+	*;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/auditd_plugins/remote/sparc/Makefile	Tue Jul 28 09:41:22 2009 +0200
@@ -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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/auditd_plugins/remote/transport.c	Tue Jul 28 09:41:22 2009 +0200
@@ -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);
+}
--- a/usr/src/pkgdefs/SUNWcsl/prototype_com	Mon Jul 27 23:54:47 2009 -0700
+++ b/usr/src/pkgdefs/SUNWcsl/prototype_com	Tue Jul 28 09:41:22 2009 +0200
@@ -352,6 +352,8 @@
 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