Mercurial > illumos > illumos-gate
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