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, 0 insertions, 1486 deletions
diff --git a/framework/src/audit/audisp/plugins/remote/audisp-remote.c b/framework/src/audit/audisp/plugins/remote/audisp-remote.c
deleted file mode 100644
index 2585c78c..00000000
--- a/framework/src/audit/audisp/plugins/remote/audisp-remote.c
+++ /dev/null
@@ -1,1486 +0,0 @@
-/* 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;
-}
-