2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A *
2N/A * send audit records to remote host
2N/A *
2N/A */
2N/A
2N/A/*
2N/A * auditd_plugin_open(), auditd_plugin() and auditd_plugin_close()
2N/A * implement a replaceable library for use by auditd; they are a
2N/A * project private interface and may change without notice.
2N/A */
2N/A
2N/A#include <arpa/inet.h>
2N/A#include <assert.h>
2N/A#include <audit_plugin.h>
2N/A#include <bsm/audit.h>
2N/A#include <bsm/audit_record.h>
2N/A#include <bsm/libbsm.h>
2N/A#include <errno.h>
2N/A#include <fcntl.h>
2N/A#include <gssapi/gssapi.h>
2N/A#include <libintl.h>
2N/A#include <netdb.h>
2N/A#include <pthread.h>
2N/A#include <rpc/rpcsec_gss.h>
2N/A#include <secdb.h>
2N/A#include <signal.h>
2N/A#include <stdio.h>
2N/A#include <stdlib.h>
2N/A#include <string.h>
2N/A#include <strings.h>
2N/A#include <ctype.h>
2N/A#include <sys/param.h>
2N/A#include <sys/socket.h>
2N/A#include <sys/types.h>
2N/A#include <unistd.h>
2N/A#include <poll.h>
2N/A
2N/A#include "audit_remote.h"
2N/A
2N/A#define DEFAULT_TIMEOUT 5 /* default connection timeout (in secs) */
2N/A#define NOSUCCESS_DELAY 20 /* unsuccessful delivery to all p_hosts */
2N/A
2N/A#define FL_SET B_TRUE /* set_fdfl(): set the flag */
2N/A#define FL_UNSET B_FALSE /* set_fdfl(): unset the flag */
2N/A
2N/Aboolean_t audit_remote_opened = B_FALSE;
2N/A
2N/Astatic int nosuccess_cnt; /* unsuccessful delivery counter */
2N/Astatic int attempts; /* per-host attempts to deliver */
2N/A
2N/Astatic int retries; /* connection retries */
2N/Aint timeout; /* connection timeout */
2N/Astatic int timeout_p_timeout; /* p_timeout attr storage */
2N/A
2N/A/* semi-exponential timeout back off; x .. attempts, y .. timeout */
2N/A#define BOFF_TIMEOUT(x, y) (x < 3 ? y * 2 * x : y * 8)
2N/A
2N/A/* general plugin lock */
2N/Apthread_mutex_t remote_plugin_mutex = PTHREAD_MUTEX_INITIALIZER;
2N/A
2N/Astatic struct hostlist_s *current_host;
2N/Astatic struct hostlist_s *failed_host;
2N/Astatic struct hostlist_s *hosts;
2N/A
2N/Aextern struct transq_hdr_s transq_hdr;
2N/Astatic long transq_count_max;
2N/Aextern pthread_mutex_t transq_lock;
2N/A
2N/Aextern pthread_t recv_tid;
2N/Aextern boolean_t recv_thread_up;
2N/A
2N/Aextern boolean_t notify_pipe_ready;
2N/Aextern int notify_pipe[2];
2N/A
2N/A#if DEBUG
2N/AFILE *dfile; /* debug file */
2N/A#endif
2N/A
2N/A/*
2N/A * set_transq_count_max() - sets the hiwater value according to the kernel
2N/A * audit queue high water mark. This is backup solution for a case, when the
2N/A * the default qsize zero value is (intentionally) set in the audit_remote(5)
2N/A * plugin configuration.
2N/A */
2N/Astatic auditd_rc_t
2N/Aset_transq_count_max(long *hiwater)
2N/A{
2N/A struct au_qctrl qctrl;
2N/A
2N/A if (auditon(A_GETQCTRL, (caddr_t)&qctrl, 0) == -1) {
2N/A return (AUDITD_RETRY);
2N/A }
2N/A
2N/A *hiwater = qctrl.aq_hiwater;
2N/A return (AUDITD_SUCCESS);
2N/A}
2N/A
2N/A/*
2N/A * get_port_default() - set the default port number; note, that "solaris-audit"
2N/A * used below in the code is the IANA assigned service name for the secure
2N/A * remote solaris audit logging.
2N/A */
2N/Astatic auditd_rc_t
2N/Aget_port_default(int *port_default)
2N/A{
2N/A
2N/A struct servent serventry;
2N/A char serventry_buf[1024];
2N/A
2N/A if (getservbyname_r("solaris-audit", "tcp", &serventry,
2N/A (char *)&serventry_buf, sizeof (serventry_buf)) == NULL) {
2N/A DPRINT((dfile, "unable to get default port number\n"));
2N/A#if DEBUG
2N/A if (errno == ERANGE) {
2N/A DPRINT((dfile, "low on buffer\n"));
2N/A }
2N/A#endif
2N/A return (AUDITD_INVALID);
2N/A }
2N/A *port_default = ntohs(serventry.s_port);
2N/A DPRINT((dfile, "default port: %d\n", *port_default));
2N/A
2N/A return (AUDITD_SUCCESS);
2N/A}
2N/A
2N/A/*
2N/A * trim_me() - trims the white space characters around the specified string.
2N/A * Inputs - pointer to the beginning of the string (str_ptr); returns - pointer
2N/A * to the trimmed string. Function returns NULL pointer in case of received
2N/A * empty string, NULL pointer or in case the pointed string consists of white
2N/A * space characters only.
2N/A */
2N/Astatic char *
2N/Atrim_me(char *str_ptr) {
2N/A
2N/A char *str_end;
2N/A
2N/A if (str_ptr == NULL || *str_ptr == '\0') {
2N/A return (NULL);
2N/A }
2N/A
2N/A while (isspace(*str_ptr)) {
2N/A str_ptr++;
2N/A }
2N/A if (*str_ptr == '\0') {
2N/A return (NULL);
2N/A }
2N/A
2N/A str_end = str_ptr + strlen(str_ptr);
2N/A
2N/A while (str_end > str_ptr && isspace(str_end[-1])) {
2N/A str_end--;
2N/A }
2N/A *str_end = '\0';
2N/A
2N/A return (str_ptr);
2N/A}
2N/A
2N/A/*
2N/A * Frees host list - should be called while keeping auditd_mutex if working with
2N/A * static hostlist_t.
2N/A */
2N/Astatic void
2N/Afreehostlist(hostlist_t **hostlist_ptr)
2N/A{
2N/A hostlist_t *h, *n;
2N/A
2N/A h = *hostlist_ptr;
2N/A
2N/A while (h != NULL) {
2N/A n = h->next_host;
2N/A freehostent(h->host);
2N/A free(h);
2N/A h = n;
2N/A }
2N/A *hostlist_ptr = NULL;
2N/A}
2N/A
2N/A#if DEBUG
2N/Avoid
2N/Ahostlist_print(hostlist_t *hostlist_node)
2N/A{
2N/A int cnt = 1;
2N/A
2N/A while (hostlist_node != NULL) {
2N/A DPRINT((dfile, "hostlist member (%d): %s\n", cnt++,
2N/A hostlist_node->host->h_name));
2N/A hostlist_node = hostlist_node->next_host;
2N/A }
2N/A}
2N/A#endif
2N/A
2N/Astatic boolean_t
2N/Ahostlist_equal(hostlist_t *hosts_a, hostlist_t *hosts_b)
2N/A{
2N/A hostlist_t *hosts_a_ptr;
2N/A hostlist_t *hosts_b_ptr;
2N/A
2N/A /* compare the amount of hosts in the hostlists */
2N/A hosts_a_ptr = hosts_a;
2N/A hosts_b_ptr = hosts_b;
2N/A while (hosts_a_ptr != NULL && hosts_b_ptr != NULL) {
2N/A hosts_a_ptr = hosts_a_ptr->next_host;
2N/A hosts_b_ptr = hosts_b_ptr->next_host;
2N/A }
2N/A if (hosts_a_ptr != NULL || hosts_b_ptr != NULL) {
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A /* reduce the head end and compare the hostlists nodes */
2N/A hosts_a_ptr = hosts_a;
2N/A hosts_b_ptr = hosts_b;
2N/A while (hosts_a_ptr != NULL) {
2N/A if (strcmp(hosts_a_ptr->host->h_name,
2N/A hosts_b_ptr->host->h_name) != 0) {
2N/A break;
2N/A }
2N/A hosts_a_ptr = hosts_a_ptr->next_host;
2N/A hosts_b_ptr = hosts_b_ptr->next_host;
2N/A }
2N/A hosts_b = hosts_b_ptr;
2N/A while (hosts_a_ptr != NULL) {
2N/A while (hosts_b_ptr != NULL) {
2N/A DPRINT((dbfp, "Compare: [%s] [%s]\n",
2N/A hosts_a_ptr->host->h_name,
2N/A hosts_b_ptr->host->h_name));
2N/A if (strcmp(hosts_a_ptr->host->h_name,
2N/A hosts_b_ptr->host->h_name) == 0) {
2N/A break;
2N/A }
2N/A hosts_b_ptr = hosts_b_ptr->next_host;
2N/A }
2N/A if (hosts_b_ptr == NULL) {
2N/A return (B_FALSE);
2N/A }
2N/A hosts_b_ptr = hosts_b;
2N/A hosts_a_ptr = hosts_a_ptr->next_host;
2N/A }
2N/A
2N/A return (B_TRUE);
2N/A}
2N/A
2N/A/*
2N/A * parsehosts() end parses the host string (hosts_str)
2N/A */
2N/Astatic auditd_rc_t
2N/Aparsehosts(char *hosts_str, hostlist_t **hostlist_new, char **error)
2N/A{
2N/A char *hostportmech, *hpm;
2N/A char *hostname;
2N/A char *port_str;
2N/A char *mech_str;
2N/A int port;
2N/A int port_default = -1;
2N/A gss_OID mech_oid;
2N/A char *lasts_hpm;
2N/A hostlist_t *lasthost = NULL;
2N/A hostlist_t *hosts_new = NULL;
2N/A hostlist_t *newhost;
2N/A struct hostent *hostentry;
2N/A int error_num;
2N/A int rc;
2N/A#if DEBUG
2N/A char addr_buf[INET6_ADDRSTRLEN];
2N/A int num_of_hosts = 0;
2N/A#endif
2N/A
2N/A DPRINT((dfile, "parsing %s\n", hosts_str));
2N/A while ((hostportmech = strtok_r(hosts_str, ",", &lasts_hpm)) != NULL) {
2N/A
2N/A hosts_str = NULL;
2N/A hostname = NULL;
2N/A port_str = NULL;
2N/A port = port_default;
2N/A mech_str = NULL;
2N/A mech_oid = GSS_C_NO_OID;
2N/A
2N/A DPRINT((dfile, "parsing host:port:mech %s\n", hostportmech));
2N/A
2N/A if (strncmp(hostportmech, ":", 1 == 0)) { /* ":port:" case */
2N/A *error = strdup(gettext("no hostname specified"));
2N/A freehostlist(&hosts_new);
2N/A return (AUDITD_INVALID);
2N/A }
2N/A
2N/A /* parse single host:port:mech target */
2N/A while ((hpm = strsep(&hostportmech, ":")) != NULL) {
2N/A
2N/A if (hostname == NULL) {
2N/A hostname = hpm;
2N/A continue;
2N/A }
2N/A if (port_str == NULL) {
2N/A port_str = hpm;
2N/A continue;
2N/A }
2N/A if (mech_str == NULL) {
2N/A mech_str = hpm;
2N/A continue;
2N/A }
2N/A
2N/A /* too many colons in the hostportmech string */
2N/A *error = strdup(gettext("invalid host:port:mech "
2N/A "specification"));
2N/A freehostlist(&hosts_new);
2N/A return (AUDITD_INVALID);
2N/A }
2N/A
2N/A if (hostname == NULL || *hostname == '\0') {
2N/A *error = strdup(gettext("invalid hostname "
2N/A "specification"));
2N/A freehostlist(&hosts_new);
2N/A return (AUDITD_INVALID);
2N/A }
2N/A
2N/A /* trim hostname */
2N/A hostname = trim_me(hostname);
2N/A if (hostname == NULL || *hostname == '\0') {
2N/A *error = strdup(gettext("empty hostname "
2N/A "specification"));
2N/A freehostlist(&hosts_new);
2N/A return (AUDITD_INVALID);
2N/A }
2N/A
2N/A DPRINT((dfile, "resolving address for %s\n", hostname));
2N/A
2N/A hostentry = getipnodebyname(hostname, AF_INET6, 0, &error_num);
2N/A if (hostentry == NULL) {
2N/A hostentry = getipnodebyname(hostname, AF_INET, 0,
2N/A &error_num);
2N/A }
2N/A if (hostentry == NULL) {
2N/A if (error_num == TRY_AGAIN) {
2N/A *error = strdup(gettext("host not found, "
2N/A "try later"));
2N/A freehostlist(&hosts_new);
2N/A return (AUDITD_RETRY);
2N/A } else {
2N/A *error = strdup(gettext("host not found"));
2N/A freehostlist(&hosts_new);
2N/A return (AUDITD_INVALID);
2N/A }
2N/A }
2N/A DPRINT((dfile, "hostentry: h_name=%s, addr_len=%d, addr=%s\n",
2N/A hostentry->h_name, hostentry->h_length,
2N/A inet_ntop(hostentry->h_addrtype,
2N/A hostentry->h_addr_list[0], addr_buf,
2N/A INET6_ADDRSTRLEN)));
2N/A
2N/A /* trim port */
2N/A port_str = trim_me(port_str);
2N/A if (port_str == NULL || *port_str == '\0') {
2N/A if (port_default == -1 &&
2N/A (rc = get_port_default(&port_default))
2N/A != AUDITD_SUCCESS) {
2N/A *error = strdup(gettext(
2N/A "unable to get default port number"));
2N/A freehostlist(&hosts_new);
2N/A return (rc);
2N/A }
2N/A port = port_default;
2N/A DPRINT((dfile, "port: %d (default)\n", port));
2N/A } else {
2N/A errno = 0;
2N/A port = atoi(port_str);
2N/A if (errno != 0 || port < 1 || port > USHRT_MAX) {
2N/A *error = strdup(gettext("invalid port number"));
2N/A freehostlist(&hosts_new);
2N/A return (AUDITD_INVALID);
2N/A }
2N/A DPRINT((dfile, "port: %d\n", port));
2N/A }
2N/A
2N/A /* trim mechanism */
2N/A mech_str = trim_me(mech_str);
2N/A if (mech_str != NULL && *mech_str != '\0') {
2N/A if (rpc_gss_mech_to_oid(mech_str, &mech_oid) != TRUE) {
2N/A *error = strdup(gettext("unknown mechanism"));
2N/A freehostlist(&hosts_new);
2N/A return (AUDITD_INVALID);
2N/A }
2N/A DPRINT((dfile, "mechanism: %s\n", mech_str));
2N/A#if DEBUG
2N/A } else {
2N/A DPRINT((dfile, "mechanism: null (default)\n"));
2N/A#endif
2N/A }
2N/A
2N/A /* add this host to host list */
2N/A newhost = malloc(sizeof (hostlist_t));
2N/A if (newhost == NULL) {
2N/A *error = strdup(gettext("no memory"));
2N/A freehostlist(&hosts_new);
2N/A return (AUDITD_NO_MEMORY);
2N/A }
2N/A newhost->host = hostentry;
2N/A newhost->port = htons(port);
2N/A newhost->mech = mech_oid;
2N/A newhost->next_host = NULL;
2N/A if (lasthost != NULL) {
2N/A lasthost->next_host = newhost;
2N/A lasthost = lasthost->next_host;
2N/A } else {
2N/A lasthost = newhost;
2N/A hosts_new = newhost;
2N/A }
2N/A#if DEBUG
2N/A num_of_hosts++;
2N/A#endif
2N/A }
2N/A *hostlist_new = hosts_new;
2N/A
2N/A#if DEBUG
2N/A DPRINT((dfile, "Configured %d hosts:\n", num_of_hosts));
2N/A hostlist_print(*hostlist_new);
2N/A#endif
2N/A
2N/A return (AUDITD_SUCCESS);
2N/A}
2N/A
2N/A
2N/A#if DEBUG
2N/Astatic char *
2N/Aauditd_message(auditd_rc_t msg_code) {
2N/A char *rc_msg;
2N/A
2N/A switch (msg_code) {
2N/A case AUDITD_SUCCESS:
2N/A rc_msg = strdup("ok");
2N/A break;
2N/A case AUDITD_RETRY:
2N/A rc_msg = strdup("retry after a delay");
2N/A break;
2N/A case AUDITD_NO_MEMORY:
2N/A rc_msg = strdup("can't allocate memory");
2N/A break;
2N/A case AUDITD_INVALID:
2N/A rc_msg = strdup("bad input");
2N/A break;
2N/A case AUDITD_COMM_FAIL:
2N/A rc_msg = strdup("communications failure");
2N/A break;
2N/A case AUDITD_FATAL:
2N/A rc_msg = strdup("other error");
2N/A break;
2N/A case AUDITD_FAIL:
2N/A rc_msg = strdup("other non-fatal error");
2N/A break;
2N/A }
2N/A return (rc_msg);
2N/A}
2N/A#endif
2N/A
2N/A/*
2N/A * rsn_to_msg() - translation of the reason of closure identifier to the more
2N/A * human readable/understandable form.
2N/A */
2N/Astatic char *
2N/Arsn_to_msg(close_rsn_t reason)
2N/A{
2N/A char *rc_msg;
2N/A
2N/A switch (reason) {
2N/A case RSN_UNDEFINED:
2N/A rc_msg = strdup(gettext("not defined reason of failure"));
2N/A break;
2N/A case RSN_INIT_POLL:
2N/A rc_msg = strdup(gettext("poll() initialization failed"));
2N/A break;
2N/A case RSN_TOK_RECV_FAILED:
2N/A rc_msg = strdup(gettext("token receiving failed"));
2N/A break;
2N/A case RSN_TOK_TOO_BIG:
2N/A rc_msg = strdup(gettext("unacceptable token size"));
2N/A break;
2N/A case RSN_TOK_UNVERIFIABLE:
2N/A rc_msg = strdup(gettext("received unverifiable token"));
2N/A break;
2N/A case RSN_SOCKET_CLOSE:
2N/A rc_msg = strdup(gettext("closed socket"));
2N/A break;
2N/A case RSN_SOCKET_CREATE:
2N/A rc_msg = strdup(gettext("socket creation failed"));
2N/A break;
2N/A case RSN_CONNECTION_CREATE:
2N/A rc_msg = strdup(gettext("connection creation failed"));
2N/A break;
2N/A case RSN_PROTOCOL_NEGOTIATE:
2N/A rc_msg = strdup(gettext("protocol negotiation failed"));
2N/A break;
2N/A case RSN_GSS_CTX_ESTABLISH:
2N/A rc_msg = strdup(gettext("context establishing failed"));
2N/A break;
2N/A case RSN_GSS_CTX_EXP:
2N/A rc_msg = strdup(gettext("context expired"));
2N/A break;
2N/A case RSN_UNKNOWN_AF:
2N/A rc_msg = strdup(gettext("unknown address family"));
2N/A break;
2N/A case RSN_MEMORY_ALLOCATE:
2N/A rc_msg = strdup(gettext("memory allocation failed"));
2N/A break;
2N/A default: /* RSN_OTHER_ERR */
2N/A rc_msg = strdup(gettext("other, not classified error"));
2N/A break;
2N/A }
2N/A return (rc_msg);
2N/A}
2N/A
2N/A/*
2N/A * set_fdfl() - based on set_fl (FL_SET/FL_UNSET) un/sets the fl flag associated
2N/A * with fd file descriptor.
2N/A */
2N/Astatic boolean_t
2N/Aset_fdfl(int fd, int fl, boolean_t set_fl)
2N/A{
2N/A int flags;
2N/A
2N/A /* power of two test - only single bit flags are allowed */
2N/A if (!fl || (fl & (fl-1))) {
2N/A DPRINT((dfile, "incorrect flag - %d isn't power of two\n", fl));
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
2N/A DPRINT((dfile, "cannot get file descriptor flags\n"));
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A if (set_fl) { /* set the fl flag */
2N/A if (flags & fl) {
2N/A return (B_TRUE);
2N/A }
2N/A
2N/A flags |= fl;
2N/A
2N/A } else { /* unset the fl flag */
2N/A if (~flags & fl) {
2N/A return (B_TRUE);
2N/A }
2N/A
2N/A flags &= ~fl;
2N/A }
2N/A
2N/A if (fcntl(fd, F_SETFL, flags) == -1) {
2N/A DPRINT((dfile, "cannot %s file descriptor flags\n",
2N/A (set_fl ? "set" : "unset")));
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A DPRINT((dfile, "fd: %d - flag: 0%o was %s\n", fd, fl,
2N/A (set_fl ? "set" : "unset")));
2N/A return (B_TRUE);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * create_notify_pipe() - creates the notification pipe. Function returns
2N/A * B_TRUE/B_FALSE on success/failure.
2N/A */
2N/Astatic boolean_t
2N/Acreate_notify_pipe(int *notify_pipe, char **error)
2N/A{
2N/A
2N/A if (pipe(notify_pipe) < 0) {
2N/A DPRINT((dfile, "Cannot create notify pipe: %s\n",
2N/A strerror(errno)));
2N/A *error = strdup(gettext("failed to create notification pipe"));
2N/A return (B_FALSE);
2N/A } else {
2N/A DPRINT((dfile, "Pipe created in:%d out:%d\n", notify_pipe[0],
2N/A notify_pipe[1]));
2N/A /* make (only) the pipe "in" end nonblocking */
2N/A if (!set_fdfl(notify_pipe[0], O_NONBLOCK, FL_UNSET) ||
2N/A !set_fdfl(notify_pipe[1], O_NONBLOCK, FL_SET)) {
2N/A DPRINT((dfile, "Cannot prepare blocking scheme on top "
2N/A "of the notification pipe: %s\n", strerror(errno)));
2N/A (void) close(notify_pipe[0]);
2N/A (void) close(notify_pipe[1]);
2N/A
2N/A *error = strdup(gettext("failed to prepare blocking "
2N/A "scheme on top of the notification pipe"));
2N/A return (B_FALSE);
2N/A }
2N/A }
2N/A
2N/A return (B_TRUE);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * auditd_plugin() sends a record via a tcp connection.
2N/A *
2N/A * Operation:
2N/A * - 1 tcp connection opened at a time, referenced by current_host->sockfd
2N/A * - tries to (open and) send a record to the current_host where its address
2N/A * is taken from the first hostent h_addr_list entry
2N/A * - if connection times out, tries second host
2N/A * - if all hosts where tried tries again for retries number of times
2N/A * - if everything fails, it bails out with AUDITD_RETRY
2N/A *
2N/A * Note, that space on stack allocated for any error message returned along
2N/A * with AUDITD_RETRY is subsequently freed by auditd.
2N/A *
2N/A */
2N/A/* ARGSUSED */
2N/Aauditd_rc_t
2N/Aauditd_plugin(const char *input, size_t in_len, uint64_t sequence,
2N/A void *plg_ctrl, char **error)
2N/A{
2N/A int rc = AUDITD_FAIL;
2N/A int send_record_rc;
2N/A char *ext_error; /* extended error string */
2N/A close_rsn_t err_rsn = RSN_UNDEFINED;
2N/A boolean_t delay_past_cycle = B_FALSE;
2N/A char *rsn_msg;
2N/A
2N/A#if DEBUG
2N/A char *rc_msg;
2N/A static uint64_t last_sequence = 0;
2N/A
2N/A if ((last_sequence > 0) && (sequence != last_sequence + 1)) {
2N/A DPRINT((dfile, "audit_remote: buffer sequence=%llu "
2N/A "but prev=%llu\n", sequence, last_sequence));
2N/A }
2N/A last_sequence = sequence;
2N/A
2N/A DPRINT((dfile, "audit_remote: input seq=%llu, len=%d\n",
2N/A sequence, in_len));
2N/A#endif
2N/A
2N/A (void) pthread_mutex_lock(&transq_lock);
2N/A if (transq_hdr.count >= transq_count_max) {
2N/A DPRINT((dfile, "Transmission queue is full (%ld)\n",
2N/A transq_hdr.count));
2N/A (void) pthread_mutex_unlock(&transq_lock);
2N/A *error = strdup(gettext("retransmission queue is full"));
2N/A return (AUDITD_RETRY);
2N/A }
2N/A (void) pthread_mutex_unlock(&transq_lock);
2N/A
2N/A
2N/A (void) pthread_mutex_lock(&remote_plugin_mutex);
2N/A DPRINT((dfile, "Trying to send record to %s [attempt:%d/%d]\n",
2N/A current_host->host->h_name, attempts + 1, retries));
2N/A
2N/Aretry:
2N/A send_record_rc = send_record(current_host, input, in_len,
2N/A sequence, &err_rsn);
2N/A switch (send_record_rc) {
2N/A case SEND_RECORD_SUCCESS:
2N/A DPRINT((dfile, "send_record() success.\n"));
2N/A nosuccess_cnt = 0;
2N/A attempts = 0;
2N/A failed_host = NULL;
2N/A rc = AUDITD_SUCCESS;
2N/A break;
2N/A case SEND_RECORD_NEXT:
2N/A DPRINT((dfile, "send_record() retry %s with penalty (rc:%d),"
2N/A " rsn:%d\n", current_host->host->h_name, send_record_rc,
2N/A err_rsn));
2N/A if (failed_host == NULL) {
2N/A failed_host = current_host;
2N/A }
2N/A attempts++;
2N/A rc = AUDITD_RETRY;
2N/A break;
2N/A case SEND_RECORD_RETRY:
2N/A DPRINT((dfile, "send_record() retry %s without penalty (rc:%d),"
2N/A " rsn:%d\n", current_host->host->h_name, send_record_rc,
2N/A err_rsn));
2N/A rc = AUDITD_RETRY;
2N/A goto retry;
2N/A default:
2N/A DPRINT((dfile, "unexpected return code: %d\n",
2N/A send_record_rc));
2N/A break;
2N/A }
2N/A
2N/A if (send_record_rc == SEND_RECORD_NEXT) {
2N/A /* warn about unsuccessful audit record delivery */
2N/A rsn_msg = rsn_to_msg(err_rsn);
2N/A if (asprintf(&ext_error, "Retrying connection %s:%d %s, "
2N/A "attempt = %d", current_host->host->h_name,
2N/A ntohs(current_host->port), rsn_msg, attempts + 1) == -1) {
2N/A free(rsn_msg);
2N/A *error = strdup(gettext("no memory"));
2N/A rc = AUDITD_NO_MEMORY;
2N/A goto out;
2N/A }
2N/A __audit_dowarn2("plugin", "audit_remote.so", "retry", ext_error,
2N/A attempts + 1);
2N/A free(rsn_msg);
2N/A free(ext_error);
2N/A
2N/A if (attempts < retries) {
2N/A /* semi-exponential timeout back off */
2N/A timeout = BOFF_TIMEOUT(attempts, timeout);
2N/A DPRINT((dfile, "New timeout=%d\n", timeout));
2N/A } else {
2N/A /* switch to next host */
2N/A current_host = current_host->next_host;
2N/A if (current_host == NULL) {
2N/A current_host = hosts;
2N/A }
2N/A timeout = timeout_p_timeout;
2N/A DPRINT((dfile, "New timeout=%d\n", timeout));
2N/A attempts = 0;
2N/A }
2N/A
2N/A /* one cycle finished */
2N/A if (current_host == failed_host && attempts == 0) {
2N/A nosuccess_cnt++;
2N/A (void) asprintf(&ext_error, "attempted to deliver "
2N/A "the audit record to all hosts defined as p_hosts "
2N/A "without success - sleeping for %d seconds",
2N/A NOSUCCESS_DELAY);
2N/A if (ext_error == NULL) {
2N/A *error = strdup(gettext("no memory"));
2N/A rc = AUDITD_NO_MEMORY;
2N/A goto out;
2N/A }
2N/A __audit_dowarn2("plugin", "audit_remote.so",
2N/A "retry", ext_error, nosuccess_cnt);
2N/A free(ext_error);
2N/A delay_past_cycle = B_TRUE;
2N/A }
2N/A
2N/A } /* if (send_record_rc == SEND_RECORD_NEXT) */
2N/A
2N/Aout:
2N/A (void) pthread_mutex_unlock(&remote_plugin_mutex);
2N/A
2N/A if (delay_past_cycle) {
2N/A DPRINT((dfile, "Sleep for %d seconds.\n", NOSUCCESS_DELAY));
2N/A (void) sleep(NOSUCCESS_DELAY);
2N/A }
2N/A
2N/A#if DEBUG
2N/A rc_msg = auditd_message(rc);
2N/A DPRINT((dfile, "audit_remote: returning: %s\n", rc_msg));
2N/A free(rc_msg);
2N/A#endif
2N/A
2N/A return (rc);
2N/A}
2N/A
2N/A/*
2N/A * auditd_plugin_open() may be called multiple times; on initial open or
2N/A * `audit -s`, then kvlist != NULL; on `audit -n`, then kvlist == NULL.
2N/A * For more information see audit(1M).
2N/A *
2N/A * Note, that space on stack allocated for any error message returned along
2N/A * with AUDITD_RETRY is subsequently freed by auditd.
2N/A *
2N/A */
2N/A/* ARGSUSED */
2N/Aauditd_rc_t
2N/Aauditd_plugin_open(const kva_t *kvlist, char **ret_list, void **plg_ctrl,
2N/A char **error)
2N/A{
2N/A kva_t *kv;
2N/A char *val_str;
2N/A int val;
2N/A long val_l;
2N/A int rc = AUDITD_SUCCESS;
2N/A int reason;
2N/A int timeout_p_timeout_new = 0;
2N/A int timeout_new = 0;
2N/A int retries_new = 0;
2N/A long transq_count_max_new = 0;
2N/A hostlist_t *hosts_new;
2N/A
2N/A *error = NULL;
2N/A *ret_list = NULL;
2N/A kv = (kva_t *)kvlist;
2N/A
2N/A#if DEBUG
2N/A dfile = __auditd_debug_file_open();
2N/A#endif
2N/A
2N/A if (audit_remote_opened) {
2N/A if (kvlist != NULL) {
2N/A DPRINT((dfile, "Action: `audit -s` (refresh)\n"));
2N/A reason = 1;
2N/A } else {
2N/A DPRINT((dfile, "Action: `audit -n`\n"));
2N/A reason = 2;
2N/A }
2N/A } else {
2N/A DPRINT((dfile, "Action: `audit -s` (initial open)\n"));
2N/A reason = 0;
2N/A }
2N/A
2N/A if (reason == 1 || reason == 0) {
2N/A val_str = kva_match(kv, "p_timeout");
2N/A if (val_str == NULL) {
2N/A *error = strdup(
2N/A gettext("p_timeout attribute not found"));
2N/A return (AUDITD_RETRY);
2N/A }
2N/A DPRINT((dfile, "val_str=%s\n", val_str));
2N/A errno = 0;
2N/A val = atoi(val_str);
2N/A if (errno == 0 && val >= 1) {
2N/A timeout_p_timeout_new = val;
2N/A timeout_new = val;
2N/A } else {
2N/A timeout_p_timeout_new = DEFAULT_TIMEOUT;
2N/A timeout_new = timeout_p_timeout_new;
2N/A DPRINT((dfile, "p_timeout set to default value: %d\n",
2N/A timeout));
2N/A }
2N/A
2N/A val_str = kva_match(kv, "p_retries");
2N/A if (val_str == NULL) {
2N/A *error = strdup(
2N/A gettext("p_retries attribute not found"));
2N/A return (AUDITD_RETRY);
2N/A }
2N/A DPRINT((dfile, "val_str=%s\n", val_str));
2N/A errno = 0;
2N/A val = atoi(val_str);
2N/A if (errno == 0 && val >= 0) {
2N/A retries_new = val;
2N/A }
2N/A
2N/A val_str = kva_match(kv, "qsize");
2N/A if (val_str == NULL) {
2N/A *error = strdup(gettext("qsize attribute not found"));
2N/A return (AUDITD_RETRY);
2N/A }
2N/A DPRINT((dfile, "qsize=%s\n", val_str));
2N/A errno = 0;
2N/A val_l = atol(val_str);
2N/A if (errno == 0 && val_l >= 0) {
2N/A transq_count_max_new = val_l;
2N/A }
2N/A if (transq_count_max_new == 0) {
2N/A rc = set_transq_count_max(&transq_count_max_new);
2N/A if (rc != AUDITD_SUCCESS) {
2N/A *error = strdup(gettext("could not get kernel "
2N/A "auditd queue high water mark\n"));
2N/A return (rc);
2N/A }
2N/A }
2N/A DPRINT((dfile, "timeout=%d, retries=%d, transq_count_max=%ld\n",
2N/A timeout, retries, transq_count_max));
2N/A
2N/A val_str = kva_match(kv, "p_hosts");
2N/A if (val_str == NULL) {
2N/A *error = strdup(gettext("no hosts configured"));
2N/A return (AUDITD_RETRY);
2N/A }
2N/A rc = parsehosts(val_str, &hosts_new, error);
2N/A if (rc != AUDITD_SUCCESS) {
2N/A return (rc);
2N/A }
2N/A
2N/A /* create the notification pipe towards the receiving thread */
2N/A if (!notify_pipe_ready) {
2N/A if (create_notify_pipe(notify_pipe, error)) {
2N/A notify_pipe_ready = B_TRUE;
2N/A } else {
2N/A return (AUDITD_RETRY);
2N/A }
2N/A }
2N/A
2N/A /* reconfiguration */
2N/A (void) pthread_mutex_lock(&transq_lock);
2N/A transq_count_max = transq_count_max_new;
2N/A (void) pthread_mutex_unlock(&transq_lock);
2N/A
2N/A (void) pthread_mutex_lock(&remote_plugin_mutex);
2N/A timeout_p_timeout = timeout_p_timeout_new;
2N/A timeout = timeout_new;
2N/A retries = retries_new;
2N/A
2N/A if (!hostlist_equal(hosts, hosts_new)) {
2N/A if (reason == 1) {
2N/A DPRINT((dfile, "hostlists do not match, "
2N/A "reset the server connection\n"));
2N/A reset_transport(DO_CLOSE, DO_NOT_SYNC);
2N/A freehostlist(&hosts);
2N/A attempts = 0;
2N/A nosuccess_cnt = 0;
2N/A }
2N/A hosts = hosts_new;
2N/A current_host = hosts_new;
2N/A } else {
2N/A freehostlist(&hosts_new);
2N/A }
2N/A (void) pthread_mutex_unlock(&remote_plugin_mutex);
2N/A
2N/A audit_remote_opened = B_TRUE;
2N/A } else {
2N/A /* audit -n (USR1) */
2N/A reset_transport(DO_CLOSE, DO_SYNC);
2N/A }
2N/A
2N/A return (AUDITD_SUCCESS);
2N/A}
2N/A
2N/A/*
2N/A * auditd_plugin_close() performs shutdown operations. The return values are
2N/A * used by auditd to output warnings via the audit_warn(1M) script and the
2N/A * string returned via "error_text", is passed to audit_warn.
2N/A *
2N/A * Note, that space on stack allocated for any error message returned along
2N/A * with AUDITD_RETRY is subsequently freed by auditd.
2N/A *
2N/A */
2N/A/* ARGSUSED */
2N/Aauditd_rc_t
2N/Aauditd_plugin_close(void **plg_ctrl, char **error)
2N/A{
2N/A reset_transport(DO_EXIT, DO_SYNC);
2N/A if (recv_thread_up && pthread_join(recv_tid, NULL) != 0) {
2N/A *error = strdup(gettext("unable to close receiving thread"));
2N/A return (AUDITD_RETRY);
2N/A }
2N/A recv_thread_up = B_FALSE;
2N/A
2N/A (void) pthread_mutex_lock(&remote_plugin_mutex);
2N/A freehostlist(&hosts);
2N/A current_host = NULL;
2N/A attempts = 0;
2N/A nosuccess_cnt = 0;
2N/A (void) pthread_mutex_unlock(&remote_plugin_mutex);
2N/A *error = NULL;
2N/A return (AUDITD_SUCCESS);
2N/A}