/* 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 * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_GSSAPI #include #include #include #endif #ifdef HAVE_LIBCAP_NG #include #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; }