aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/audit/audisp/plugins/remote/audisp-remote.c
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/audit/audisp/plugins/remote/audisp-remote.c')
-rw-r--r--framework/src/audit/audisp/plugins/remote/audisp-remote.c1486
1 files changed, 1486 insertions, 0 deletions
diff --git a/framework/src/audit/audisp/plugins/remote/audisp-remote.c b/framework/src/audit/audisp/plugins/remote/audisp-remote.c
new file mode 100644
index 00000000..2585c78c
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/audisp-remote.c
@@ -0,0 +1,1486 @@
+/* audisp-remote.c --
+ * Copyright 2008-2012 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Authors:
+ * Steve Grubb <sgrubb@redhat.com>
+ *
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <signal.h>
+#include <syslog.h>
+#include <string.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/select.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#ifdef USE_GSSAPI
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_generic.h>
+#include <krb5.h>
+#endif
+#ifdef HAVE_LIBCAP_NG
+#include <cap-ng.h>
+#endif
+#include "libaudit.h"
+#include "private.h"
+#include "remote-config.h"
+#include "queue.h"
+#include "remote-fgets.h"
+
+#define CONFIG_FILE "/etc/audisp/audisp-remote.conf"
+#define BUF_SIZE 32
+
+/* MAX_AUDIT_MESSAGE_LENGTH, aligned to 4 KB so that an average q_append() only
+ writes to two disk disk blocks (1 aligned data block, 1 header block). */
+#define QUEUE_ENTRY_SIZE (3*4096)
+
+/* Error types */
+#define ET_SUCCESS 0
+#define ET_PERMANENT -1
+#define ET_TEMPORARY -2
+
+/* Global Data */
+static volatile int stop = 0;
+static volatile int hup = 0;
+static volatile int suspend = 0;
+static volatile int dump = 0;
+static volatile int transport_ok = 0;
+static volatile int sock=-1;
+static volatile int remote_ended = 0, quiet = 0;
+static int ifd;
+remote_conf_t config;
+
+/* Constants */
+static const char *SINGLE = "1";
+static const char *HALT = "0";
+static const char *INIT_PGM = "/sbin/init";
+static const char *SPOOL_FILE = "/var/spool/audit/remote.log";
+
+/* Local function declarations */
+static int check_message(void);
+static int relay_event(const char *s, size_t len);
+static int init_transport(void);
+static int stop_transport(void);
+static int ar_read (int, void *, int);
+static int ar_write (int, const void *, int);
+
+#ifdef USE_GSSAPI
+/* We only ever talk to one server, so we don't need per-connection
+ credentials. These are the ones we talk to the server with. */
+gss_ctx_id_t my_context;
+
+#define REQ_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG
+#define USE_GSS (config.enable_krb5)
+#endif
+
+/* Compile-time expression verification */
+#define verify(E) do { \
+ char verify__[(E) ? 1 : -1]; \
+ (void)verify__; \
+ } while (0)
+
+/*
+ * SIGTERM handler
+ */
+static void term_handler( int sig )
+{
+ stop = 1;
+}
+
+/*
+ * SIGHUP handler: re-read config
+ */
+static void hup_handler( int sig )
+{
+ hup = 1;
+}
+
+static void reload_config(void)
+{
+ stop_transport(); // FIXME: We should only stop transport if necessary
+ hup = 0;
+}
+
+/*
+ * SIGSUR1 handler: dump stats
+ */
+static void user1_handler( int sig )
+{
+ dump = 1;
+}
+
+static void dump_stats(struct queue *queue)
+{
+ syslog(LOG_INFO, "suspend=%s, transport_ok=%s, queue_size=%zu",
+ suspend ? "yes" : "no",
+ transport_ok ? "yes" : "no",
+ q_queue_length(queue));
+ dump = 0;
+}
+
+/*
+ * SIGSUR2 handler: resume logging
+ */
+static void user2_handler( int sig )
+{
+ suspend = 0;
+}
+
+/*
+ * SIGCHLD handler: reap exiting processes
+ */
+static void child_handler(int sig)
+{
+ while (waitpid(-1, NULL, WNOHANG) > 0)
+ ; /* empty */
+}
+
+/*
+ * Handlers for various events coming back from the remote server.
+ * Return -1 if the remote dispatcher should exit.
+ */
+
+/* Loss of sync - got an invalid response. */
+static int sync_error_handler (const char *why)
+{
+ /* "why" has human-readable details on why we've lost (or will
+ be losing) sync. Sync errors are transient - if a retry
+ doesn't fix it, we eventually call network_failure_handler
+ which has all the user-tweakable actions. */
+ syslog (LOG_ERR, "lost/losing sync, %s", why);
+ return 0;
+}
+
+static void change_runlevel(const char *level)
+{
+ char *argv[3];
+ int pid;
+
+ pid = fork();
+ if (pid < 0) {
+ syslog(LOG_ALERT,
+ "audisp-remote failed to fork switching runlevels");
+ return;
+ }
+ if (pid) /* Parent */
+ return;
+
+ /* Child */
+ argv[0] = (char *)INIT_PGM;
+ argv[1] = (char *)level;
+ argv[2] = NULL;
+ execve(INIT_PGM, argv, NULL);
+ syslog(LOG_ALERT, "audisp-remote failed to exec %s", INIT_PGM);
+ exit(1);
+}
+
+static void safe_exec(const char *exe, const char *message)
+{
+ char *argv[3];
+ int pid;
+
+ if (exe == NULL) {
+ syslog(LOG_ALERT,
+ "Safe_exec passed NULL for program to execute");
+ return;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ syslog(LOG_ALERT,
+ "audisp-remote failed to fork doing safe_exec");
+ return;
+ }
+ if (pid) /* Parent */
+ return;
+
+ /* Child */
+ argv[0] = (char *)exe;
+ argv[1] = (char *)message;
+ argv[2] = NULL;
+ execve(exe, argv, NULL);
+ syslog(LOG_ALERT, "audisp-remote failed to exec %s", exe);
+ exit(1);
+}
+
+static int do_action (const char *desc, const char *message,
+ int log_level,
+ failure_action_t action, const char *exe)
+{
+ switch (action)
+ {
+ case FA_IGNORE:
+ return 0;
+ case FA_SYSLOG:
+ syslog (log_level, "%s, %s", desc, message);
+ return 0;
+ case FA_EXEC:
+ safe_exec (exe, message);
+ return 0;
+ case FA_SUSPEND:
+ syslog (log_level,
+ "suspending remote logging due to %s", desc);
+ suspend = 1;
+ return 0;
+ case FA_RECONNECT:
+ syslog (log_level,
+ "remote logging disconnected due to %s, will attempt reconnection",
+ desc);
+ return 0;
+ case FA_SINGLE:
+ syslog (log_level,
+ "remote logging is switching system to single user mode due to %s",
+ desc);
+ change_runlevel(SINGLE);
+ return -1;
+ case FA_HALT:
+ syslog (log_level,
+ "remote logging halting system due to %s", desc);
+ change_runlevel(HALT);
+ return -1;
+ case FA_STOP:
+ syslog (log_level, "remote logging stopping due to %s, %s",
+ desc, message);
+ stop = 1;
+ return -1;
+ }
+ syslog (log_level, "unhandled action %d for %s", action, desc);
+ return -1;
+}
+
+static int network_failure_handler (const char *message)
+{
+ return do_action ("network failure", message,
+ LOG_WARNING,
+ config.network_failure_action,
+ config.network_failure_exe);
+}
+
+static int remote_disk_low_handler (const char *message)
+{
+ return do_action ("remote server is low on disk space", message,
+ LOG_WARNING,
+ config.disk_low_action, config.disk_low_exe);
+}
+
+static int remote_disk_full_handler (const char *message)
+{
+ return do_action ("remote server's disk is full", message,
+ LOG_ERR,
+ config.disk_full_action, config.disk_full_exe);
+}
+
+static int remote_disk_error_handler (const char *message)
+{
+ return do_action ("remote server has a disk error", message,
+ LOG_ERR,
+ config.disk_error_action, config.disk_error_exe);
+}
+
+static int remote_server_ending_handler (const char *message)
+{
+ stop_transport();
+ remote_ended = 1;
+ return do_action ("remote server is going down", message,
+ LOG_NOTICE,
+ config.remote_ending_action,
+ config.remote_ending_exe);
+}
+
+static int generic_remote_error_handler (const char *message)
+{
+ return do_action ("unrecognized remote error", message,
+ LOG_ERR, config.generic_error_action,
+ config.generic_error_exe);
+}
+
+static int generic_remote_warning_handler (const char *message)
+{
+ return do_action ("unrecognized remote warning", message,
+ LOG_WARNING,
+ config.generic_warning_action,
+ config.generic_warning_exe);
+}
+
+/* Report and handle a queue error, using errno. */
+static void queue_error(void)
+{
+ char *errno_str;
+
+ errno_str = strerror(errno);
+ do_action("queue error", errno_str, LOG_ERR, config.queue_error_action,
+ config.queue_error_exe);
+}
+
+static void send_heartbeat (void)
+{
+ relay_event (NULL, 0);
+}
+
+static void do_overflow_action(void)
+{
+ switch (config.overflow_action)
+ {
+ case OA_IGNORE:
+ break;
+ case OA_SYSLOG:
+ syslog(LOG_ERR, "queue is full - dropping event");
+ break;
+ case OA_SUSPEND:
+ syslog(LOG_ALERT,
+ "Audisp-remote is suspending event processing due to overflowing its queue.");
+ suspend = 1;
+ break;
+ case OA_SINGLE:
+ syslog(LOG_ALERT,
+ "Audisp-remote is now changing the system to single user mode due to overflowing its queue");
+ change_runlevel(SINGLE);
+ break;
+ case OA_HALT:
+ syslog(LOG_ALERT,
+ "Audisp-remote is now halting the system due to overflowing its queue");
+ change_runlevel(HALT);
+ break;
+ default:
+ syslog(LOG_ALERT, "Unknown overflow action requested");
+ break;
+ }
+}
+
+/* Initialize and return a queue depending on user's configuration.
+ On error return NULL and set errno. */
+static struct queue *init_queue(void)
+{
+ const char *path;
+ int q_flags;
+
+ if (config.queue_file != NULL)
+ path = config.queue_file;
+ else
+ path = SPOOL_FILE;
+ q_flags = Q_IN_MEMORY;
+ if (config.mode == M_STORE_AND_FORWARD)
+ /* FIXME: let user control Q_SYNC? */
+ q_flags |= Q_IN_FILE | Q_CREAT | Q_RESIZE;
+ verify(QUEUE_ENTRY_SIZE >= MAX_AUDIT_MESSAGE_LENGTH);
+ return q_open(q_flags, path, config.queue_depth, QUEUE_ENTRY_SIZE);
+}
+
+/* Send a record from QUEUE to the remote system */
+static void send_one(struct queue *queue)
+{
+ char event[MAX_AUDIT_MESSAGE_LENGTH];
+ int len;
+
+ if (suspend || !transport_ok)
+ return;
+
+ len = q_peek(queue, event, sizeof(event));
+ if (len == 0)
+ return;
+ if (len < 0) {
+ queue_error();
+ return;
+ }
+
+ /* We send len -1 to remove trailing \n */
+ if (relay_event(event, len-1) < 0)
+ return;
+
+ if (q_drop_head(queue) != 0)
+ queue_error();
+}
+
+int main(int argc, char *argv[])
+{
+ struct sigaction sa;
+ struct queue *queue;
+ int rc;
+ size_t q_len;
+
+ /* Register sighandlers */
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ /* Set handler for the ones we care about */
+ sa.sa_handler = term_handler;
+ sigaction(SIGTERM, &sa, NULL);
+ sa.sa_handler = hup_handler;
+ sigaction(SIGHUP, &sa, NULL);
+ sa.sa_handler = user1_handler;
+ sigaction(SIGUSR1, &sa, NULL);
+ sa.sa_handler = user2_handler;
+ sigaction(SIGUSR2, &sa, NULL);
+ sa.sa_handler = child_handler;
+ sigaction(SIGCHLD, &sa, NULL);
+ if (load_config(&config, CONFIG_FILE))
+ return 6;
+
+ (void) umask( umask( 077 ) | 027 );
+ // ifd = open("test.log", O_RDONLY);
+ ifd = 0;
+ fcntl(ifd, F_SETFL, O_NONBLOCK);
+
+ /* We fail here if the transport can't be initialized because of some
+ * permanent (i.e. operator) problem, such as misspelled host name. */
+ rc = init_transport();
+ if (rc == ET_PERMANENT)
+ return 1;
+ queue = init_queue();
+ if (queue == NULL) {
+ syslog(LOG_ERR, "Error initializing audit record queue: %m");
+ return 1;
+ }
+
+#ifdef HAVE_LIBCAP_NG
+ // Drop capabilities
+ capng_clear(CAPNG_SELECT_BOTH);
+ if (config.local_port && config.local_port < 1024)
+ capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED,
+ CAP_NET_BIND_SERVICE);
+ capng_apply(CAPNG_SELECT_BOTH);
+#endif
+ syslog(LOG_NOTICE, "Audisp-remote started with queue_size: %zu",
+ q_queue_length(queue));
+
+ while (stop == 0) { //FIXME break out when socket is closed
+ fd_set rfd, wfd;
+ struct timeval tv;
+ char event[MAX_AUDIT_MESSAGE_LENGTH];
+ int n, fds = ifd + 1;
+
+ /* Load configuration */
+ if (hup)
+ reload_config();
+
+ if (dump)
+ dump_stats(queue);
+
+ /* Setup select flags */
+ FD_ZERO(&rfd);
+ FD_SET(ifd, &rfd); // input fd
+ FD_ZERO(&wfd);
+ if (sock > 0) {
+ // Setup socket to read acks from server
+ FD_SET(sock, &rfd); // remote socket
+ if (sock > ifd)
+ fds = sock + 1;
+ // If we have anything in the queue,
+ // find out if we can send it
+ if (q_queue_length(queue) && !suspend && transport_ok)
+ FD_SET(sock, &wfd);
+ }
+
+ if (config.heartbeat_timeout > 0) {
+ tv.tv_sec = config.heartbeat_timeout;
+ tv.tv_usec = 0;
+ n = select(fds, &rfd, &wfd, NULL, &tv);
+ } else
+ n = select(fds, &rfd, &wfd, NULL, NULL);
+ if (n < 0)
+ continue; // If here, we had some kind of problem
+
+ if ((config.heartbeat_timeout > 0) && n == 0 && !remote_ended) {
+ /* We attempt a hearbeat if select fails, which
+ * may give us more heartbeats than we need. This
+ * is safer than too few heartbeats. */
+ quiet = 1;
+ send_heartbeat();
+ quiet = 0;
+ continue;
+ }
+
+ // See if we got a shutdown message from the server
+ if (sock > 0 && FD_ISSET(sock, &rfd))
+ check_message();
+
+ // If we broke out due to one of these, cycle to start
+ if (hup != 0 || stop != 0)
+ continue;
+
+ // See if input fd is also set
+ if (FD_ISSET(ifd, &rfd)) {
+ do {
+ if (remote_fgets(event, sizeof(event), ifd)) {
+ if (!transport_ok && remote_ended &&
+ config.remote_ending_action ==
+ FA_RECONNECT) {
+ quiet = 1;
+ if (init_transport() ==
+ ET_SUCCESS)
+ remote_ended = 0;
+ quiet = 0;
+ }
+ /* Strip out EOE records */
+ if (*event == 't') {
+ if (strncmp(event,
+ "type=EOE", 8) == 0)
+ continue;
+ } else {
+ char *ptr = strchr(event, ' ');
+ if (ptr) {
+ ptr++;
+ if (strncmp(ptr,
+ "type=EOE",
+ 8) == 0)
+ continue;
+ } else
+ continue; //malformed
+ }
+ if (q_append(queue, event) != 0) {
+ if (errno == ENOSPC)
+ do_overflow_action();
+ else
+ queue_error();
+ }
+ } else if (remote_fgets_eof())
+ stop = 1;
+ } while (remote_fgets_more(sizeof(event)));
+ }
+ // See if output fd is also set
+ if (sock > 0 && FD_ISSET(sock, &wfd)) {
+ // If so, try to drain backlog
+ while (q_queue_length(queue) && !suspend &&
+ !stop && transport_ok)
+ send_one(queue);
+ }
+ }
+ if (sock >= 0) {
+ shutdown(sock, SHUT_RDWR);
+ close(sock);
+ }
+ free_config(&config);
+ q_len = q_queue_length(queue);
+ q_close(queue);
+ if (stop)
+ syslog(LOG_NOTICE, "audisp-remote is exiting on stop request, queue_size: %zu", q_len);
+
+ return q_len ? 1 : 0;
+}
+
+#ifdef USE_GSSAPI
+
+/* Communications under GSS is done by token exchanges. Each "token" may
+ contain a message, perhaps signed, perhaps encrypted. The messages within
+ are what we're interested in, but the network sees the tokens. The
+ protocol we use for transferring tokens is to send the length first,
+ four bytes MSB first, then the token data. We return nonzero on error. */
+static int recv_token(int s, gss_buffer_t tok)
+{
+ int ret;
+ unsigned char lenbuf[4];
+ unsigned int len;
+
+ ret = ar_read(s, (char *) lenbuf, 4);
+ if (ret < 0) {
+ syslog(LOG_ERR, "GSS-API error reading token length");
+ return -1;
+ } else if (!ret) {
+ return 0;
+ } else if (ret != 4) {
+ syslog(LOG_ERR, "GSS-API error reading token length");
+ return -1;
+ }
+
+ len = ( ((uint32_t)(lenbuf[0] & 0xFF) << 24)
+ | ((uint32_t)(lenbuf[1] & 0xFF) << 16)
+ | ((uint32_t)(lenbuf[2] & 0xFF) << 8)
+ | (uint32_t)(lenbuf[3] & 0xFF));
+
+ if (len > MAX_AUDIT_MESSAGE_LENGTH) {
+ syslog(LOG_ERR,
+ "GSS-API error: event length excedes MAX_AUDIT_LENGTH");
+ return -1;
+ }
+ tok->length = len;
+ tok->value = (char *) malloc(tok->length ? tok->length : 1);
+ if (tok->length && tok->value == NULL) {
+ syslog(LOG_ERR, "Out of memory allocating token data %zd %zx",
+ tok->length, tok->length);
+ return -1;
+ }
+
+ ret = ar_read(s, (char *) tok->value, tok->length);
+ if (ret < 0) {
+ syslog(LOG_ERR, "GSS-API error reading token data");
+ free(tok->value);
+ return -1;
+ } else if (ret != (int) tok->length) {
+ syslog(LOG_ERR, "GSS-API error reading token data");
+ free(tok->value);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Same here. */
+int send_token(int s, gss_buffer_t tok)
+{
+ int ret;
+ unsigned char lenbuf[4];
+ unsigned int len;
+
+ if (tok->length > 0xffffffffUL)
+ return -1;
+
+ len = tok->length;
+ lenbuf[0] = (len >> 24) & 0xff;
+ lenbuf[1] = (len >> 16) & 0xff;
+ lenbuf[2] = (len >> 8) & 0xff;
+ lenbuf[3] = len & 0xff;
+
+ ret = ar_write(s, (char *) lenbuf, 4);
+ if (ret < 0) {
+ syslog(LOG_ERR, "GSS-API error sending token length");
+ return -1;
+ } else if (ret != 4) {
+ syslog(LOG_ERR, "GSS-API error sending token length");
+ return -1;
+ }
+
+ ret = ar_write(s, tok->value, tok->length);
+ if (ret < 0) {
+ syslog(LOG_ERR, "GSS-API error sending token data");
+ return -1;
+ } else if (ret != (int) tok->length) {
+ syslog(LOG_ERR, "GSS-API error sending token data");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void gss_failure_2 (const char *msg, int status, int type)
+{
+ OM_uint32 message_context = 0;
+ OM_uint32 min_status = 0;
+ gss_buffer_desc status_string;
+
+ do {
+ gss_display_status (&min_status,
+ status,
+ type,
+ GSS_C_NO_OID,
+ &message_context,
+ &status_string);
+
+ syslog (LOG_ERR, "GSS error: %s: %s",
+ msg, (char *)status_string.value);
+
+ gss_release_buffer(&min_status, &status_string);
+ } while (message_context != 0);
+}
+
+static void gss_failure (const char *msg, int major_status, int minor_status)
+{
+ gss_failure_2 (msg, major_status, GSS_C_GSS_CODE);
+ if (minor_status)
+ gss_failure_2 (msg, minor_status, GSS_C_MECH_CODE);
+}
+
+#define KCHECK(x,f) if (x) { \
+ syslog (LOG_ERR, "krb5 error: %s in %s\n", krb5_get_error_message (kcontext, x), f); \
+ return -1; }
+
+#define KEYTAB_NAME "/etc/audisp/audisp-remote.key"
+#define CCACHE_NAME "MEMORY:audisp-remote"
+
+/* Each time we connect to the server, we negotiate a set of credentials and
+ a security context. To do this, we need our own credentials first. For
+ other Kerberos applications, the user will have called kinit (or otherwise
+ authenticated) first, but we don't have that luxury. So, we implement part
+ of kinit here. When our tickets expire, the usual close/open/retry logic
+ has us calling here again, where we re-init and get new tickets. */
+static int negotiate_credentials (void)
+{
+ gss_buffer_desc empty_token_buf = { 0, (void *) "" };
+ gss_buffer_t empty_token = &empty_token_buf;
+ gss_buffer_desc send_tok, recv_tok, *token_ptr;
+ gss_ctx_id_t *gss_context = &my_context;
+ gss_buffer_desc name_buf;
+ gss_name_t service_name_e;
+ OM_uint32 major_status, minor_status, init_sec_min_stat;
+ OM_uint32 ret_flags;
+
+ /* Getting an initial ticket is outside the scope of GSS, so
+ we use Kerberos calls here. */
+
+ int krberr;
+ krb5_context kcontext = NULL;
+ char *realm_name;
+ krb5_principal audit_princ;
+ krb5_ccache ccache = NULL;
+ krb5_creds my_creds;
+ krb5_get_init_creds_opt options;
+ krb5_keytab keytab = NULL;
+ const char *krb5_client_name;
+ char *slashptr;
+ char host_name[255];
+ struct stat st;
+ const char *key_file;
+
+ token_ptr = GSS_C_NO_BUFFER;
+ *gss_context = GSS_C_NO_CONTEXT;
+ recv_tok.value = NULL;
+
+ krberr = krb5_init_context (&kcontext);
+ KCHECK (krberr, "krb5_init_context");
+
+ if (config.krb5_key_file)
+ key_file = config.krb5_key_file;
+ else
+ key_file = KEYTAB_NAME;
+ unsetenv ("KRB5_KTNAME");
+ setenv ("KRB5_KTNAME", key_file, 1);
+
+ if (stat (key_file, &st) == 0) {
+ if ((st.st_mode & 07777) != 0400) {
+ if (!quiet)
+ syslog (LOG_ERR,
+ "%s is not mode 0400 (it's %#o) - compromised key?",
+ key_file, st.st_mode & 07777);
+ return -1;
+ }
+ if (st.st_uid != 0) {
+ if (!quiet)
+ syslog (LOG_ERR,
+ "%s is not owned by root (it's %d) - compromised key?",
+ key_file, st.st_uid);
+ return -1;
+ }
+ }
+
+ /* This looks up the default real (*our* realm) from
+ /etc/krb5.conf (or wherever) */
+ krberr = krb5_get_default_realm (kcontext, &realm_name);
+ KCHECK (krberr, "krb5_get_default_realm");
+
+ krb5_client_name = config.krb5_client_name ?
+ config.krb5_client_name : "auditd";
+ if (gethostname(host_name, sizeof(host_name)) != 0) {
+ if (!quiet)
+ syslog (LOG_ERR,
+ "gethostname: host name longer than %ld characters?",
+ sizeof (host_name));
+ return -1;
+ }
+
+ syslog (LOG_ERR, "kerberos principal: %s/%s@%s\n",
+ krb5_client_name, host_name, realm_name);
+ /* Encode our own "name" as auditd/remote@EXAMPLE.COM. */
+ krberr = krb5_build_principal (kcontext, &audit_princ,
+ strlen(realm_name), realm_name,
+ krb5_client_name, host_name, NULL);
+ KCHECK (krberr, "krb5_build_principal");
+
+ /* Locate our machine's key table, where our private key is
+ * held. */
+ krberr = krb5_kt_resolve (kcontext, key_file, &keytab);
+ KCHECK (krberr, "krb5_kt_resolve");
+
+ /* Identify a cache to hold the key in. The GSS wrappers look
+ up our credentials here. */
+ krberr = krb5_cc_resolve (kcontext, CCACHE_NAME, &ccache);
+ KCHECK (krberr, "krb5_cc_resolve");
+
+ setenv("KRB5CCNAME", CCACHE_NAME, 1);
+
+ memset(&my_creds, 0, sizeof(my_creds));
+ memset(&options, 0, sizeof(options));
+ krb5_get_init_creds_opt_set_address_list(&options, NULL);
+ krb5_get_init_creds_opt_set_forwardable(&options, 0);
+ krb5_get_init_creds_opt_set_proxiable(&options, 0);
+ krb5_get_init_creds_opt_set_tkt_life(&options, 24*60*60);
+
+ /* Load our credentials from the key table. */
+ krberr = krb5_get_init_creds_keytab(kcontext, &my_creds, audit_princ,
+ keytab, 0, NULL,
+ &options);
+ KCHECK (krberr, "krb5_get_init_creds_keytab");
+
+ /* Create the cache... */
+ krberr = krb5_cc_initialize(kcontext, ccache, audit_princ);
+ KCHECK (krberr, "krb5_cc_initialize");
+
+ /* ...and store our credentials in it. */
+ krberr = krb5_cc_store_cred(kcontext, ccache, &my_creds);
+ KCHECK (krberr, "krb5_cc_store_cred");
+
+ /* The GSS code now has a set of credentials for this program.
+ I.e. we know who "we" are. Now we talk to the server to
+ get its credentials and set up a security context for encryption. */
+ if (config.krb5_principal == NULL) {
+ const char *name = config.krb5_client_name ?
+ config.krb5_client_name : "auditd";
+ config.krb5_principal = (char *) malloc (strlen (name) + 1
+ + strlen (config.remote_server) + 1);
+ sprintf((char *)config.krb5_principal, "%s@%s",
+ name, config.remote_server);
+ }
+ slashptr = strchr (config.krb5_principal, '/');
+ if (slashptr)
+ *slashptr = '@';
+
+ name_buf.value = (char *)config.krb5_principal;
+ name_buf.length = strlen(name_buf.value) + 1;
+ major_status = gss_import_name(&minor_status, &name_buf,
+ (gss_OID) gss_nt_service_name, &service_name_e);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("importing name", major_status, minor_status);
+ return -1;
+ }
+
+ /* Someone has to go first. In this case, it's us. */
+ if (send_token(sock, empty_token) < 0) {
+ (void) gss_release_name(&minor_status, &service_name_e);
+ return -1;
+ }
+
+ /* The server starts this loop with the token we just sent
+ (the empty one). We start this loop with "no token". */
+ token_ptr = GSS_C_NO_BUFFER;
+ *gss_context = GSS_C_NO_CONTEXT;
+
+ do {
+ /* Give GSS a chance to digest what we have so far. */
+ major_status = gss_init_sec_context(&init_sec_min_stat,
+ GSS_C_NO_CREDENTIAL, gss_context,
+ service_name_e, NULL, REQ_FLAGS, 0,
+ NULL, /* no channel bindings */
+ token_ptr, NULL, /* ignore mech type */
+ &send_tok, &ret_flags, NULL); /* ignore time_rec */
+
+ if (token_ptr != GSS_C_NO_BUFFER)
+ free(recv_tok.value);
+
+ /* Send the server any tokens requested of us. */
+ if (send_tok.length != 0) {
+ if (send_token(sock, &send_tok) < 0) {
+ (void) gss_release_buffer(&minor_status,
+ &send_tok);
+ (void) gss_release_name(&minor_status,
+ &service_name_e);
+ return -1;
+ }
+ }
+ (void) gss_release_buffer(&minor_status, &send_tok);
+
+ if (major_status != GSS_S_COMPLETE
+ && major_status != GSS_S_CONTINUE_NEEDED) {
+ gss_failure("initializing context", major_status,
+ init_sec_min_stat);
+ (void) gss_release_name(&minor_status, &service_name_e);
+ if (*gss_context != GSS_C_NO_CONTEXT)
+ gss_delete_sec_context(&minor_status,
+ gss_context, GSS_C_NO_BUFFER);
+ return -1;
+ }
+
+ /* Now get any tokens the sever sends back. We use
+ these back at the top of the loop. */
+ if (major_status == GSS_S_CONTINUE_NEEDED) {
+ if (recv_token(sock, &recv_tok) < 0) {
+ (void) gss_release_name(&minor_status,
+ &service_name_e);
+ return -1;
+ }
+ token_ptr = &recv_tok;
+ }
+ } while (major_status == GSS_S_CONTINUE_NEEDED);
+
+ (void) gss_release_name(&minor_status, &service_name_e);
+
+#if 0
+ major_status = gss_inquire_context (&minor_status, &my_context, NULL,
+ &service_name_e, NULL, NULL,
+ NULL, NULL, NULL);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("inquiring target name", major_status, minor_status);
+ return -1;
+ }
+ major_status = gss_display_name(&minor_status, service_name_e,
+ &recv_tok, NULL);
+ gss_release_name(&minor_status, &service_name_e);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("displaying name", major_status, minor_status);
+ return -1;
+ }
+ syslog(LOG_INFO, "GSS-API Connected to: %s",
+ (char *)recv_tok.value);
+#endif
+ return 0;
+}
+#endif
+
+static int stop_sock(void)
+{
+ if (sock >= 0) {
+ shutdown(sock, SHUT_RDWR);
+ close(sock);
+ }
+ sock = -1;
+ transport_ok = 0;
+
+ return 0;
+}
+
+static int stop_transport(void)
+{
+ int rc;
+
+ switch (config.transport)
+ {
+ case T_TCP:
+ rc = stop_sock();
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+ return rc;
+}
+
+static int init_sock(void)
+{
+ int rc;
+ struct addrinfo *ai;
+ struct addrinfo hints;
+ char remote[BUF_SIZE];
+ int one=1;
+
+ if (sock >= 0) {
+ syslog(LOG_NOTICE, "socket already setup");
+ transport_ok = 1;
+ return ET_SUCCESS;
+ }
+ memset(&hints, '\0', sizeof(hints));
+ hints.ai_flags = AI_ADDRCONFIG|AI_NUMERICSERV;
+ hints.ai_socktype = SOCK_STREAM;
+ snprintf(remote, BUF_SIZE, "%u", config.port);
+ rc = getaddrinfo(config.remote_server, remote, &hints, &ai);
+ if (rc) {
+ if (!quiet)
+ syslog(LOG_ERR,
+ "Error looking up remote host: %s - exiting",
+ gai_strerror(rc));
+ if (rc == EAI_NONAME || rc == EAI_NODATA)
+ return ET_PERMANENT;
+ else
+ return ET_TEMPORARY;
+ }
+ sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (sock < 0) {
+ if (!quiet)
+ syslog(LOG_ERR, "Error creating socket: %s",
+ strerror(errno));
+ freeaddrinfo(ai);
+ return ET_TEMPORARY;
+ }
+
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof (int));
+
+ if (config.local_port != 0) {
+ struct sockaddr_in address;
+
+ memset (&address, 0, sizeof(address));
+ address.sin_family = AF_INET;
+ address.sin_port = htons(config.local_port);
+ address.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ if (bind(sock, (struct sockaddr *)&address, sizeof(address))) {
+ if (!quiet)
+ syslog(LOG_ERR,
+ "Cannot bind local socket to port %d",
+ config.local_port);
+ stop_sock();
+ freeaddrinfo(ai);
+ return ET_TEMPORARY;
+ }
+
+ }
+ if (connect(sock, ai->ai_addr, ai->ai_addrlen)) {
+ if (!quiet)
+ syslog(LOG_ERR, "Error connecting to %s: %s",
+ config.remote_server, strerror(errno));
+ freeaddrinfo(ai);
+ stop_sock();
+ return ET_TEMPORARY;
+ }
+
+ freeaddrinfo(ai);
+ setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof (int));
+
+ /* The idea here is to minimize the time between the message
+ and the ACK, assuming that individual messages are
+ infrequent enough that we can ignore the inefficiency of
+ sending the header and message in separate packets. */
+ if (config.format == F_MANAGED)
+ setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
+ (char *)&one, sizeof (int));
+
+#ifdef USE_GSSAPI
+ if (USE_GSS) {
+ if (negotiate_credentials ())
+ return ET_PERMANENT;
+ }
+#endif
+
+ transport_ok = 1;
+ syslog(LOG_NOTICE, "Connected to %s", config.remote_server);
+ return ET_SUCCESS;
+}
+
+static int init_transport(void)
+{
+ int rc;
+
+ switch (config.transport)
+ {
+ case T_TCP:
+ rc = init_sock();
+ // We set this so that it will retry the connection
+ if (rc == ET_TEMPORARY)
+ remote_ended = 1;
+ break;
+ default:
+ rc = ET_PERMANENT;
+ break;
+ }
+ return rc;
+}
+
+static int ar_write (int sk, const void *buf, int len)
+{
+ int rc = 0, r;
+ while (len > 0) {
+ do {
+ r = write(sk, buf, len);
+ } while (r < 0 && errno == EINTR);
+ if (r < 0) {
+ if (errno == EPIPE)
+ stop_sock();
+ return r;
+ }
+ if (r == 0)
+ break;
+ rc += r;
+ buf = (void *)((char *)buf + r);
+ len -= r;
+ }
+ return rc;
+}
+
+static int ar_read (int sk, void *buf, int len)
+{
+ int rc = 0, r, timeout = config.max_time_per_record * 1000;
+ struct pollfd pfd;
+
+ pfd.fd=sk;
+ pfd.events=POLLIN | POLLPRI | POLLHUP | POLLERR | POLLNVAL;
+ while (len > 0) {
+ do {
+ // reads can hang if cable is disconnected
+ int prc = poll(&pfd, (nfds_t) 1, timeout);
+ if (prc <= 0)
+ return -1;
+ r = read(sk, buf, len);
+ } while (r < 0 && errno == EINTR);
+ if (r < 0) {
+ if (errno == EPIPE)
+ stop_sock();
+ return r;
+ }
+ if (r == 0)
+ break;
+ rc += r;
+ buf = (void *)((char *)buf + r);
+ len -= r;
+ }
+ return rc;
+}
+
+static int relay_sock_ascii(const char *s, size_t len)
+{
+ int rc;
+
+ if (len == 0)
+ return 0;
+
+ if (!transport_ok) {
+ if (init_transport ())
+ return -1;
+ }
+
+ rc = ar_write(sock, s, len);
+ if (rc <= 0) {
+ stop = 1;
+ syslog(LOG_ERR,"Connection to %s closed unexpectedly - exiting",
+ config.remote_server);
+ return -1;
+ }
+
+ return 0;
+}
+
+#ifdef USE_GSSAPI
+
+/* Sending an encrypted message is pretty simple - wrap the message in
+ a token, and send the token. The server unwraps it to get the
+ original message. */
+static int send_msg_gss (unsigned char *header, const char *msg, uint32_t mlen)
+{
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc utok, etok;
+ int rc;
+
+ utok.length = AUDIT_RMW_HEADER_SIZE + mlen;
+ utok.value = malloc (utok.length);
+
+ memcpy (utok.value, header, AUDIT_RMW_HEADER_SIZE);
+
+ if (msg != NULL && mlen > 0)
+ memcpy (utok.value+AUDIT_RMW_HEADER_SIZE, msg, mlen);
+
+ major_status = gss_wrap (&minor_status,
+ my_context,
+ 1,
+ GSS_C_QOP_DEFAULT,
+ &utok,
+ NULL,
+ &etok);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("encrypting message", major_status, minor_status);
+ free (utok.value);
+ return -1;
+ }
+ rc = send_token (sock, &etok);
+ free (utok.value);
+ (void) gss_release_buffer(&minor_status, &etok);
+
+ return rc ? -1 : 0;
+}
+
+/* Likewise here. */
+static int recv_msg_gss (unsigned char *header, char *msg, uint32_t *mlen)
+{
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc utok, etok;
+ int hver, mver, rc;
+ uint32_t type, rlen, seq;
+
+ rc = recv_token (sock, &etok);
+ if (rc)
+ return -1;
+
+ major_status = gss_unwrap (&minor_status, my_context, &etok,
+ &utok, NULL, NULL);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("decrypting message", major_status, minor_status);
+ free (utok.value);
+ return -1;
+ }
+
+ if (utok.length < AUDIT_RMW_HEADER_SIZE) {
+ sync_error_handler ("message too short");
+ return -1;
+ }
+ memcpy (header, utok.value, AUDIT_RMW_HEADER_SIZE);
+
+ if (! AUDIT_RMW_IS_MAGIC (header, AUDIT_RMW_HEADER_SIZE)) {
+ sync_error_handler ("bad magic number");
+ return -1;
+ }
+
+ AUDIT_RMW_UNPACK_HEADER (header, hver, mver, type, rlen, seq);
+
+ if (rlen > MAX_AUDIT_MESSAGE_LENGTH) {
+ sync_error_handler ("message too long");
+ return -1;
+ }
+
+ memcpy (msg, utok.value+AUDIT_RMW_HEADER_SIZE, rlen);
+
+ *mlen = rlen;
+
+ return 0;
+}
+#endif
+
+static int send_msg_tcp (unsigned char *header, const char *msg, uint32_t mlen)
+{
+ int rc;
+
+ rc = ar_write(sock, header, AUDIT_RMW_HEADER_SIZE);
+ if (rc <= 0) {
+ syslog(LOG_ERR, "send to %s failed", config.remote_server);
+ return 1;
+ }
+
+ if (msg != NULL && mlen > 0) {
+ rc = ar_write(sock, msg, mlen);
+ if (rc <= 0) {
+ syslog(LOG_ERR, "send to %s failed",
+ config.remote_server);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int recv_msg_tcp (unsigned char *header, char *msg, uint32_t *mlen)
+{
+ int hver, mver, rc;
+ uint32_t type, rlen, seq;
+
+ rc = ar_read (sock, header, AUDIT_RMW_HEADER_SIZE);
+ if (rc < 16) {
+ syslog(LOG_ERR, "read from %s failed", config.remote_server);
+ return -1;
+ }
+
+ if (! AUDIT_RMW_IS_MAGIC (header, AUDIT_RMW_HEADER_SIZE)) {
+ /* FIXME: the right thing to do here is close the socket
+ * and start a new one. */
+ sync_error_handler ("bad magic number");
+ return -1;
+ }
+
+ AUDIT_RMW_UNPACK_HEADER (header, hver, mver, type, rlen, seq);
+
+ if (rlen > MAX_AUDIT_MESSAGE_LENGTH) {
+ sync_error_handler ("message too long");
+ return -1;
+ }
+
+ if (rlen > 0 && ar_read (sock, msg, rlen) < rlen) {
+ sync_error_handler ("ran out of data reading reply");
+ return -1;
+ }
+ return 0;
+}
+
+static int check_message_managed(void)
+{
+ unsigned char header[AUDIT_RMW_HEADER_SIZE];
+ int hver, mver;
+ uint32_t type, rlen, seq;
+ char msg[MAX_AUDIT_MESSAGE_LENGTH+1];
+
+#ifdef USE_GSSAPI
+ if (USE_GSS) {
+ if (recv_msg_gss (header, msg, &rlen)) {
+ stop_transport();
+ return -1;
+ }
+ } else
+#endif
+ if (recv_msg_tcp(header, msg, &rlen)) {
+ stop_transport();
+ return -1;
+ }
+
+ AUDIT_RMW_UNPACK_HEADER(header, hver, mver, type, rlen, seq);
+ msg[rlen] = 0;
+
+ if (type == AUDIT_RMW_TYPE_ENDING)
+ return remote_server_ending_handler(msg);
+ if (type == AUDIT_RMW_TYPE_DISKLOW)
+ return remote_disk_low_handler(msg);
+ if (type == AUDIT_RMW_TYPE_DISKFULL)
+ return remote_disk_full_handler(msg);
+ if (type == AUDIT_RMW_TYPE_DISKERROR)
+ return remote_disk_error_handler(msg);
+ return -1;
+}
+
+/* This is to check for async notification like server is shutting down */
+static int check_message(void)
+{
+ int rc;
+
+ switch (config.format)
+ {
+ case F_MANAGED:
+ rc = check_message_managed();
+ break;
+/* case F_ASCII:
+ rc = check_message_ascii();
+ break; */
+ default:
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
+static int relay_sock_managed(const char *s, size_t len)
+{
+ static int sequence_id = 1;
+ unsigned char header[AUDIT_RMW_HEADER_SIZE];
+ int hver, mver;
+ uint32_t type, rlen, seq;
+ char msg[MAX_AUDIT_MESSAGE_LENGTH+1];
+ int n_tries_this_message = 0;
+ time_t now, then = 0;
+
+ sequence_id ++;
+
+try_again:
+ time (&now);
+ if (then == 0)
+ then = now;
+
+ /* We want the first retry to be quick, in case the network
+ failed for some fail-once reason. In this case, it goes
+ "failure - reconnect - send". Only if this quick retry
+ fails do we start pausing between retries to prevent
+ swamping the local computer and the network. */
+ if (n_tries_this_message > 1)
+ sleep (config.network_retry_time);
+
+ if (n_tries_this_message > config.max_tries_per_record) {
+ network_failure_handler ("max retries exhausted");
+ return -1;
+ }
+ if ((now - then) > config.max_time_per_record) {
+ network_failure_handler ("max retry time exhausted");
+ return -1;
+ }
+
+ n_tries_this_message ++;
+
+ if (!transport_ok) {
+ if (init_transport ())
+ goto try_again;
+ }
+
+ type = (s != NULL) ? AUDIT_RMW_TYPE_MESSAGE : AUDIT_RMW_TYPE_HEARTBEAT;
+ AUDIT_RMW_PACK_HEADER (header, 0, type, len, sequence_id);
+
+#ifdef USE_GSSAPI
+ if (USE_GSS) {
+ if (send_msg_gss (header, s, len)) {
+ stop_transport ();
+ goto try_again;
+ }
+ } else
+#endif
+ if (send_msg_tcp (header, s, len)) {
+ stop_transport ();
+ goto try_again;
+ }
+
+#ifdef USE_GSSAPI
+ if (USE_GSS) {
+ if (recv_msg_gss (header, msg, &rlen)) {
+ stop_transport ();
+ goto try_again;
+ }
+ } else
+#endif
+ if (recv_msg_tcp (header, msg, &rlen)) {
+ stop_transport ();
+ goto try_again;
+ }
+
+ AUDIT_RMW_UNPACK_HEADER (header, hver, mver, type, rlen, seq);
+ msg[rlen] = 0;
+
+ /* Handle this first. It doesn't matter if seq compares or not
+ * since the other end is going down...deal with it. */
+ if (type == AUDIT_RMW_TYPE_ENDING)
+ return remote_server_ending_handler (msg);
+
+ if (seq != sequence_id) {
+ /* FIXME: should we read another header and
+ see if it matches? If so, we need to deal
+ with timeouts. */
+ if (sync_error_handler ("mismatched response"))
+ return -1;
+ stop_transport();
+ goto try_again;
+ }
+
+ /* Specific errors we know how to deal with. */
+ if (type == AUDIT_RMW_TYPE_DISKLOW)
+ return remote_disk_low_handler (msg);
+ if (type == AUDIT_RMW_TYPE_DISKFULL)
+ return remote_disk_full_handler (msg);
+ if (type == AUDIT_RMW_TYPE_DISKERROR)
+ return remote_disk_error_handler (msg);
+
+ /* Generic errors. */
+ if (type & AUDIT_RMW_TYPE_FATALMASK)
+ return generic_remote_error_handler (msg);
+ if (type & AUDIT_RMW_TYPE_WARNMASK)
+ return generic_remote_warning_handler (msg);
+
+ return 0;
+}
+
+static int relay_sock(const char *s, size_t len)
+{
+ int rc;
+
+ switch (config.format)
+ {
+ case F_MANAGED:
+ rc = relay_sock_managed (s, len);
+ break;
+ case F_ASCII:
+ rc = relay_sock_ascii (s, len);
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
+/* Send audit event to remote system */
+static int relay_event(const char *s, size_t len)
+{
+ int rc;
+
+ switch (config.transport)
+ {
+ case T_TCP:
+ rc = relay_sock(s, len);
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+